123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #pragma once
- #include <AzCore/Component/Component.h>
- #include <Vegetation/Ebuses/AreaSystemRequestBus.h>
- #include <Vegetation/Ebuses/AreaRequestBus.h>
- #include <Vegetation/Ebuses/SystemConfigurationBus.h>
- #include <AzCore/std/parallel/semaphore.h>
- #include <AzCore/Component/TickBus.h>
- #include <AzCore/std/parallel/thread.h>
- #include <GradientSignal/Ebuses/SectorDataRequestBus.h>
- #include <SurfaceData/SurfaceDataSystemNotificationBus.h>
- #include <CrySystemBus.h>
- #include <ISystem.h>
- #include <AzFramework/Terrain/TerrainDataRequestBus.h>
- namespace Vegetation
- {
- struct DebugData;
- enum class SnapMode : AZ::u8
- {
- Corner = 0,
- Center
- };
- /**
- * The configuration for managing areas mostly the dimensions of the sectors
- */
- class AreaSystemConfig
- : public AZ::ComponentConfig
- {
- public:
- AZ_CLASS_ALLOCATOR(AreaSystemConfig, AZ::SystemAllocator);
- AZ_RTTI(AreaSystemConfig, "{14CCBE43-52DD-4F56-92A8-2BB011A0F7A2}", AZ::ComponentConfig);
- static void Reflect(AZ::ReflectContext* context);
- bool operator == (const AreaSystemConfig& other) const
- {
- return m_viewRectangleSize == other.m_viewRectangleSize
- && m_sectorDensity == other.m_sectorDensity
- && m_sectorSizeInMeters == other.m_sectorSizeInMeters
- && m_threadProcessingIntervalMs == other.m_threadProcessingIntervalMs
- && m_sectorSearchPadding == other.m_sectorSearchPadding
- && m_sectorPointSnapMode == other.m_sectorPointSnapMode;
- }
- int m_viewRectangleSize = 13;
- int m_sectorDensity = 20;
- int m_sectorSizeInMeters = 16;
- int m_threadProcessingIntervalMs = 500;
- int m_sectorSearchPadding = 0;
- SnapMode m_sectorPointSnapMode = SnapMode::Corner;
- private:
- static const int s_maxViewRectangleSize;
- static const int s_maxSectorDensity;
- static const int s_maxSectorSizeInMeters;
- static const int s_maxInstancesPerMeter;
- static const int64_t s_maxVegetationInstances;
- AZ::Outcome<void, AZStd::string> ValidateViewArea(void* newValue, const AZ::Uuid& valueType);
- AZ::Outcome<void, AZStd::string> ValidateSectorDensity(void* newValue, const AZ::Uuid& valueType);
- AZ::Outcome<void, AZStd::string> ValidateSectorSize(void* newValue, const AZ::Uuid& valueType);
- };
- /**
- * Manages an sectors and claims while the camera scrolls through the 3D world
- */
- class AreaSystemComponent
- : public AZ::Component
- , private AZ::TickBus::Handler
- , private AreaSystemRequestBus::Handler
- , private GradientSignal::SectorDataRequestBus::Handler
- , private SystemConfigurationRequestBus::Handler
- , private CrySystemEventBus::Handler
- , private ISystemEventListener
- , private SurfaceData::SurfaceDataSystemNotificationBus::Handler
- , private AzFramework::Terrain::TerrainDataNotificationBus::Handler
- {
- public:
- friend class EditorAreaSystemComponent;
- AZ_COMPONENT(AreaSystemComponent, "{7CE8E791-6BC6-4C88-8727-A476DE00F9A1}");
- static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
- static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
- static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);
- static void Reflect(AZ::ReflectContext* context);
- AreaSystemComponent(const AreaSystemConfig& configuration);
- AreaSystemComponent() = default;
- ~AreaSystemComponent() = default;
- //////////////////////////////////////////////////////////////////////////
- // AZ::Component interface implementation
- void Init() override;
- void Activate() override;
- void Deactivate() override;
- bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
- bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
- //////////////////////////////////////////////////////////////////////////
- // AreaSystemRequestBus
- void RegisterArea(AZ::EntityId areaId, AZ::u32 layer, AZ::u32 priority, const AZ::Aabb& bounds) override;
- void UnregisterArea(AZ::EntityId areaId) override;
- void RefreshArea(AZ::EntityId areaId, AZ::u32 layer, AZ::u32 priority, const AZ::Aabb& bounds) override;
- void RefreshAllAreas() override;
- void ClearAllAreas() override;
- void MuteArea(AZ::EntityId areaId) override;
- void UnmuteArea(AZ::EntityId areaId) override;
- void EnumerateInstancesInOverlappingSectors(const AZ::Aabb& bounds, AreaSystemEnumerateCallback callback) const override;
- void EnumerateInstancesInAabb(const AZ::Aabb& bounds, AreaSystemEnumerateCallback callback) const override;
- AZStd::size_t GetInstanceCountInAabb(const AZ::Aabb& bounds) const override;
- AZStd::vector<Vegetation::InstanceData> GetInstancesInAabb(const AZ::Aabb& bounds) const override;
- //////////////////////////////////////////////////////////////////////////
- // GradientSignal::SectorDataRequestBus
- void GetPointsPerMeter(float& value) const override;
- //////////////////////////////////////////////////////////////////////////
- // AZ::TickBus
- void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
- //////////////////////////////////////////////////////////////////////////
- // SystemConfigurationRequestBus
- void UpdateSystemConfig(const AZ::ComponentConfig* config) override;
- void GetSystemConfig(AZ::ComponentConfig* config) const override;
- //////////////////////////////////////////////////////////////////////////
- // SurfaceData::SurfaceDataSystemNotificationBus
- void OnSurfaceChanged(
- const AZ::EntityId& entityId,
- const AZ::Aabb& oldBounds,
- const AZ::Aabb& newBounds,
- const SurfaceData::SurfaceTagSet& changedSurfaceTags) override;
- ////////////////////////////////////////////////////////////////////////////
- // CrySystemEvents
- void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) override;
- void OnCrySystemShutdown(ISystem& system) override;
- void OnCryEditorBeginLevelExport() override;
- void OnCryEditorEndLevelExport(bool /*success*/) override;
- void OnCryEditorCloseScene() override;
- //////////////////////////////////////////////////////////////////////////
- // ISystemEventListener
- void OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam) override;
- //////////////////////////////////////////////////////////////////////////
- // TerrainDataNotificationBus
- void OnTerrainDataCreateBegin() override;
- void OnTerrainDataDestroyBegin() override;
- private:
- using ClaimContainer = AZStd::unordered_map<ClaimHandle, InstanceData>;
- using ClaimContainerEntry = AZStd::pair<ClaimHandle, InstanceData>;
- using SectorId = AZStd::pair<int, int>;
- // SectorInfo contains basic sector information and the set of "plantable points" in the sector that have been claimed
- struct SectorInfo
- {
- AZ_CLASS_ALLOCATOR(SectorInfo, AZ::SystemAllocator);
- SectorId m_id = {};
- AZ::Aabb m_bounds = {};
- //! Keeps track of points that have been claimed. This is not cleared at the start of an update pass
- ClaimContainer m_claimedWorldPoints;
- //! Keeps track of previous state of sector while filling to avoid redundant instance destroy/create calls
- ClaimContainer m_claimedWorldPointsBeforeFill;
- ClaimContext m_baseContext;
- int GetSectorX() const { return m_id.first; }
- int GetSectorY() const { return m_id.second; }
- };
- // VegetationAreaInfo contains the basic information we need for tracking which vegetation areas to apply
- // to which sectors, and in which order.
- struct VegetationAreaInfo
- {
- AZ_CLASS_ALLOCATOR(VegetationAreaInfo, AZ::SystemAllocator);
- AZ::EntityId m_id;
- AZ::Aabb m_bounds = {};
- AZ::u32 m_layer = {};
- AZ::u32 m_priority = {};
- };
- using VegetationAreaMap = AZStd::unordered_map<AZ::EntityId, VegetationAreaInfo>;
- using VegetationAreaSet = AZStd::unordered_set<AZ::EntityId>;
- using VegetationAreaVector = AZStd::vector<VegetationAreaInfo>;
- using UnregisteredVegetationAreaMap = AZStd::unordered_map<SectorId, AZStd::unordered_set<AZ::EntityId>>;
- //! Helper class to track whether or not a visible sector is dirty. Different instances of this
- //! class are used to track different reasons for being dirty.
- //! This is a class instead of just an unordered_set<> so that we can also encapsulate the optimization
- //! of tracking when *all* sectors are dirty.
- class DirtySectors
- {
- public:
- DirtySectors() = default;
- ~DirtySectors() = default;
- void MarkDirty(const SectorId& id);
- void MarkAllDirty();
- bool IsAllDirty() const { return m_allSectorsDirty; }
- bool IsNoneDirty() const { return (!m_allSectorsDirty) && m_dirtySet.empty(); }
- bool IsDirty(const SectorId& id) const;
- void Clear();
- private:
- using DirtySectorSet = AZStd::unordered_set<SectorId>;
- DirtySectorSet m_dirtySet;
- //! Flag when *all* existing sectors are dirty
- bool m_allSectorsDirty = false;
- };
- // ViewRect is a helper struct to manage the "scrolling view rectangle". This view rectangle controls the
- // set of active spawned vegetation.
- struct ViewRect
- {
- int m_x = 0;
- int m_y = 0;
- int m_width = 0;
- int m_height = 0;
- AZ::Aabb m_viewRectBounds = AZ::Aabb::CreateNull();
- ViewRect() = default;
- ViewRect(int inX, int inY, int inW, int inH, AZ::Aabb viewRectBounds)
- : m_x(inX)
- , m_y(inY)
- , m_width(inW)
- , m_height(inH)
- , m_viewRectBounds(viewRectBounds)
- {
- }
- bool IsInside(const SectorId& sector) const;
- ViewRect Overlap(const ViewRect& b) const;
- bool operator !=(const ViewRect& b);
- bool operator ==(const ViewRect& b);
- size_t GetNumSectors() const;
- int GetMinXSector() const { return m_x; }
- int GetMinYSector() const { return m_y; }
- int GetMaxXSector() const { return m_x + m_width - 1; }
- int GetMaxYSector() const { return m_y + m_height - 1; }
- SectorId GetMinSector() const { return SectorId(GetMinXSector(), GetMinYSector()); }
- SectorId GetMaxSector() const { return SectorId(GetMaxXSector(), GetMaxYSector()); }
- AZ::Aabb GetViewRectBounds() const { return m_viewRectBounds; }
- };
- // Forward declarations, these get defined further down.
- class UpdateContext;
- class PersistentThreadData;
- // Thread-local copies of main state. We make copies of this to ensure that we can process sectors safely on
- // the vegetation thread while these values potentially get changed on the main thread without needing to wrap
- // all access with mutexes.
- struct CachedMainThreadData
- {
- float m_worldToSector = 0.0f;
- ViewRect m_currViewRect = {};
- int m_sectorSizeInMeters = 0;
- int m_sectorDensity = 0;
- SnapMode m_sectorPointSnapMode = SnapMode::Corner;
- };
- // VegetationThreadTasks is the task queue that's used equally by the main thread and the vegetation thread.
- // The main thread generally queues the tasks, and the vegetation thread processes them.
- // Any data used from this class requires mutexes, atomics, or other thread protection.
- class VegetationThreadTasks
- {
- public:
- void QueueVegetationTask(AZStd::function<void(UpdateContext* context, PersistentThreadData* threadData, VegetationThreadTasks * vegTasks)> func);
- void ProcessVegetationThreadTasks(UpdateContext* context, PersistentThreadData* threadData);
- bool VegetationThreadTasksPending()
- {
- AZStd::lock_guard<decltype(m_vegetationThreadTaskMutex)> lock(m_vegetationThreadTaskMutex);
- return !m_vegetationThreadTasks.empty();
- }
- //! Get sector by 2d veg map coordinates.
- const SectorInfo* GetSector(const SectorId& sectorId) const;
- SectorInfo* GetSector(const SectorId& sectorId);
- SectorInfo* CreateSector(const SectorId& sectorId, int sectorDensity, int sectorSizeInMeters, SnapMode sectorPointSnapMode);
- void UpdateSectorPoints(SectorInfo& sectorInfo, int sectorDensity, int sectorSizeInMeters, SnapMode sectorPointSnapMode);
- void FillSector(SectorInfo& sectorInfo, const VegetationAreaVector& activeAreas);
- void DeleteSector(const SectorId& sectorId);
- void ClearSectors();
- //! Gets the AABB for a sector
- static AZ::Aabb GetSectorBounds(const SectorId& sectorId, int sectorSizeInMeters);
- void FetchDebugData();
- void MarkDirtySectors(const AZ::Aabb& bounds, DirtySectors& dirtySet, float worldToSector, const ViewRect& viewRect);
- void AddUnregisteredVegetationArea(const VegetationAreaInfo& area, float worldToSector, const ViewRect& viewRect);
- //! 2D Array rolling window of sectors that store vegetation objects.
- using SectorRollingWindow = AZStd::unordered_map<SectorId, SectorInfo>;
- mutable AZStd::recursive_mutex m_sectorRollingWindowMutex;
- SectorRollingWindow m_sectorRollingWindow;
- private:
- // claiming logic
- void CreateClaim(SectorInfo& sectorInfo, const ClaimHandle handle, const InstanceData& instanceData);
- ClaimHandle CreateClaimHandle(const SectorInfo& sectorInfo, uint32_t index) const;
- void ReleaseUnusedClaims(SectorInfo& sectorInfo);
- void ReleaseUnregisteredClaims(SectorInfo& sectorInfo);
- //! Creates a new sector
- void UpdateSectorCallbacks(SectorInfo& sectorInfo);
- static void EmptySector(SectorInfo& sectorInfo);
- // Calls the given function on each sector in the box
- template<class Fn>
- static void EnumerateSectorsInAabb(const AZ::Aabb& bounds, float worldToSector, const ViewRect& viewRect, Fn&& fn);
- //! Queued list of vegetation area state update requests. These get queued on the main thread, and processed
- //! on the vegetation thread.
- mutable AZStd::recursive_mutex m_vegetationThreadTaskMutex;
- using VegetationThreadTaskList = AZStd::list<AZStd::function<void(UpdateContext* context, PersistentThreadData* threadData, VegetationThreadTasks* vegTasks)>>;
- VegetationThreadTaskList m_vegetationThreadTasks;
- //! Map from sectors to areas affecting that sector which have been unregistered and need to have their claims released
- //! Note: This is only updated from the vegetation thread when processing vegetation tasks.
- UnregisteredVegetationAreaMap m_unregisteredVegetationAreaSet;
- //! Cached pointer to the debug data.
- //! Note: This doesn't have an associated mutex because DebugData itself consists purely of atomics
- DebugData* m_debugData = nullptr;
- };
- //! Helper struct to hold the state data used by the vegetation thread. This contains all the data
- //! that should persist between thread runs, which lets us completely shut down the thread when there's
- //! no work to do.
- //! This also contains the vegetation thread mutex and state variables, which are accessed by both threads
- //! to manage synchronization.
- class PersistentThreadData
- {
- public:
- // This mutex is active the entire time the vegetation thread is running. Its main purpose
- // is to ensure we don't have component activations / deactivations that occur while the vegetation
- // thread is still processing.
- mutable AZStd::recursive_mutex m_vegetationThreadMutex;
- // Current state of the vegetation thread
- enum class VegetationThreadState
- {
- Stopped,
- Running,
- InterruptRequested
- };
- AZStd::atomic<VegetationThreadState> m_vegetationThreadState{ VegetationThreadState::Stopped };
- // Current state of data synchronization between main thread and vegetation thread
- enum class VegetationDataSyncState
- {
- Synchronized,
- Dirty,
- Updating
- };
- AZStd::atomic<VegetationDataSyncState> m_vegetationDataSyncState{ VegetationDataSyncState::Synchronized };
- //! Reset the states that can get recalculated when the vegetation thread is run.
- //! This does *not* reset the states on registered vegetation area lists, since those only
- //! get filled out once.
- void Init()
- {
- m_activeAreasDirty = true;
- m_activeAreas.clear();
- m_activeAreasInBubble.clear();
- m_dirtySectorContents.Clear();
- m_dirtySectorSurfacePoints.Clear();
- }
- void InterruptVegetationThread();
- // The following state is public because it's accessed by queued vegetation tasks and UpdateContext. It should
- // eventually be encapsulated a little better.
- //! set of sectors that need their contents refreshed
- DirtySectors m_dirtySectorContents;
- //! set of sectors that need their surface points recalculated (which implies also needing the contents refreshed)
- DirtySectors m_dirtySectorSurfacePoints;
- VegetationAreaMap m_globalVegetationAreaMap;
- VegetationAreaSet m_ignoredVegetationAreaSet;
- //! Determines when to refresh the set of active areas
- bool m_activeAreasDirty = true;
- protected:
- // Only UpdateContext is allowed to directly access the persisted thread state.
- friend class UpdateContext;
- // This is effectively a local variable in UpdateActiveVegetationAreas, but is kept
- // persistent to avoid potentially frequent reallocation.
- VegetationAreaVector m_activeAreas;
- //! The set of active vegetation areas that overlap the current view rectangle
- VegetationAreaVector m_activeAreasInBubble;
- };
- // UpdateContext is the logic that normally runs on the vegetation thread. It processes all of the logic needed to update and fill
- // any vegetation sectors that are currently within the view rectangle. Occasionally the logic will be triggered on the main thread
- // in cases such as shutdown when the vegetation thread isn't running any we need to perform the cleanup synchronously.
- class UpdateContext
- {
- public:
- UpdateContext() = default;
- void Run(PersistentThreadData* threadData, VegetationThreadTasks* vegTasks, CachedMainThreadData* cachedMainThreadData);
- void UpdateActiveVegetationAreas(PersistentThreadData* threadData, const ViewRect& viewRect);
- const CachedMainThreadData& GetCachedMainThreadData() { return m_cachedMainThreadData; }
- private:
- bool UpdateSectorWorkLists(PersistentThreadData* threadData, VegetationThreadTasks* vegTasks);
- bool UpdateOneSector(PersistentThreadData* threadData, VegetationThreadTasks* vegTasks);
- enum class UpdateMode
- {
- Create,
- RebuildSurfaceCacheAndFill,
- Fill
- };
- // The sorted work list of sectors to delete. The list is recreated every time UpdateSectorWorkLists() is run.
- AZStd::vector<SectorId> m_deleteWorkList;
- // The sorted work list of sectors to create / update. This is incrementally modified when UpdateSectorWorkLists()
- // is run, because any previously-requested updates that are still in view need to be preserved. They can't simply
- // be recalculated.
- AZStd::vector<AZStd::pair<SectorId, UpdateMode>> m_updateWorkList;
- // Sector counts of the number of expected sectors in the view rectangle vs the number of sectors
- // currently active. These are used to "load balance" sector deletes and creates so that we don't have
- // too many sectors active at any one point in time.
- size_t m_viewRectSectorCount = 0;
- // Thread-local copy of the main thread's m_cachedMainThreadData. This way we can read from it on the vegetation
- // thread without requiring mutexes.
- CachedMainThreadData m_cachedMainThreadData;
- };
- bool ApplyPendingConfigChanges();
- void ReleaseData();
- void ReleaseAllClaims();
- void ReleaseWithoutCleanup();
- bool CalculateViewRect();
- //! Get sector id by world coordinates.
- static SectorId GetSectorId(const AZ::Vector3& worldPos, float worldToSector);
- // All of this state data should only get accessed by the main thread. A subset of this data gets copied
- // into CachedMainThreadData for the vegetation thread to be able to query in a lockless manner.
- AreaSystemConfig m_configuration;
- float m_worldToSector = 0.0f; //! world to sector scaling ratio.
- ViewRect m_currViewRect = {};
- float m_vegetationThreadTaskTimer = 0.0f;
- ISystem* m_system = nullptr;
- bool m_configDirty = false;
- AreaSystemConfig m_pendingConfigUpdate;
- // The vegetation task queue gets read/written from both threads, and uses atomics + mutexes for synchronization.
- VegetationThreadTasks m_vegTasks;
- // This state should only get read or written from the vegetation thread, except for component initialization.
- PersistentThreadData m_threadData;
- // This state gets written to from the main thread, and gets copied and read from the vegetation thread.
- CachedMainThreadData m_cachedMainThreadData;
- };
- }
|