// Copyright Epic Games, Inc. All Rights Reserved. #include "MassEntityTestFarmPlot.h" #include "Components/HierarchicalInstancedStaticMeshComponent.h" #include "Engine/CollisionProfile.h" #include "MassExecutor.h" #include "MassExecutionContext.h" #include "MassProcessingContext.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MassEntityTestFarmPlot) //@TODO: Can add a ReadyToHarvest tag Fragment on when things are ready to harvest, to stop them ticking and signal that we need to create an icon //----------------------------------------------------------------------------- // UFarmProcessorBase //----------------------------------------------------------------------------- UFarmProcessorBase::UFarmProcessorBase() : EntityQuery(*this) { // not auto-registering to manually control execution bAutoRegisterWithProcessingPhases = false; } //----------------------------------------------------------------------------- // UFarmWaterProcessor //----------------------------------------------------------------------------- void UFarmWaterProcessor::ConfigureQueries(const TSharedRef& EntityManager) { EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); } void UFarmWaterProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) { QUICK_SCOPE_CYCLE_COUNTER(UFarmWaterProcessor_Run); EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context) { const float DeltaTimeSeconds = Context.GetDeltaTimeSeconds(); TArrayView WaterList = Context.GetMutableFragmentView(); for (FFarmWaterFragment& WaterFragment : WaterList) { WaterFragment.CurrentWater = FMath::Clamp(WaterFragment.CurrentWater + WaterFragment.DeltaWaterPerSecond * DeltaTimeSeconds, 0.0f, 1.0f); } }); } //----------------------------------------------------------------------------- // UFarmHarvestTimerSystem_Flowers //----------------------------------------------------------------------------- void UFarmHarvestTimerSystem_Flowers::ConfigureQueries(const TSharedRef& EntityManager) { EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); } void UFarmHarvestTimerSystem_Flowers::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) { QUICK_SCOPE_CYCLE_COUNTER(UFarmHarvestTimerSystem_Flowers_Run); EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context) { const float WellWateredThreshold = 0.25f; TArrayView TimerList = Context.GetMutableFragmentView(); TConstArrayView WaterList = Context.GetFragmentView(); TArrayView FlowerList = Context.GetMutableFragmentView(); for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { if (TimerList[EntityIt].NumSecondsLeft > 0) { --TimerList[EntityIt].NumSecondsLeft; if (WaterList[EntityIt].CurrentWater > WellWateredThreshold) { ++FlowerList[EntityIt].NumBonusTicks; } } } }); } //----------------------------------------------------------------------------- // UFarmHarvestTimerSystem_Crops //----------------------------------------------------------------------------- void UFarmHarvestTimerSystem_Crops::ConfigureQueries(const TSharedRef& EntityManager) { EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::None, EMassFragmentPresence::All); } void UFarmHarvestTimerSystem_Crops::Execute(FMassEntityManager & EntityManager, FMassExecutionContext & Context) { QUICK_SCOPE_CYCLE_COUNTER(UFarmHarvestTimerSystem_Crops_Run); EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context) { const float WellWateredThreshold = 0.25f; TArrayView TimerList = Context.GetMutableFragmentView(); TConstArrayView WaterList = Context.GetFragmentView(); for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { const uint32 TimeToSubtract = (WaterList[EntityIt].CurrentWater > WellWateredThreshold) ? 2 : 1; TimerList[EntityIt].NumSecondsLeft = (TimerList[EntityIt].NumSecondsLeft >= TimeToSubtract) ? (TimerList[EntityIt].NumSecondsLeft - TimeToSubtract) : 0; } }); } //----------------------------------------------------------------------------- // UFarmHarvestTimerExpired //----------------------------------------------------------------------------- void UFarmHarvestTimerExpired::ConfigureQueries(const TSharedRef& EntityManager) { EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddTagRequirement(EMassFragmentPresence::None); EntityQuery.AddTagRequirement(EMassFragmentPresence::None); } void UFarmHarvestTimerExpired::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) { QUICK_SCOPE_CYCLE_COUNTER(UFarmHarvestTimerExpired_Run); EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context) { TConstArrayView TimerList = Context.GetFragmentView(); for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { if (TimerList[EntityIt].NumSecondsLeft == 0) { Context.Defer().AddTag(Context.GetEntity(EntityIt)); } } }); } //----------------------------------------------------------------------------- // UFarmHarvestTimerSetIcon //----------------------------------------------------------------------------- void UFarmHarvestTimerSetIcon::ConfigureQueries(const TSharedRef& EntityManager) { EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddTagRequirement(EMassFragmentPresence::All); } void UFarmHarvestTimerSetIcon::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) { if (HarvestIconISMC == nullptr) { return; } QUICK_SCOPE_CYCLE_COUNTER(SET_ICON_SET_ICON_SET_ICON); EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context) { TConstArrayView GridCoordList = Context.GetFragmentView(); TArrayView VisualList = Context.GetMutableFragmentView(); for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { const FFarmGridCellData& GridCells = GridCoordList[EntityIt]; const FVector IconPosition(GridCells.CellX*GridCellWidth, GridCells.CellY*GridCellHeight, HarvestIconHeight); const FTransform IconTransform(FQuat::Identity, IconPosition, FVector(HarvestIconScale, HarvestIconScale, HarvestIconScale)); VisualList[EntityIt].HarvestIconIndex = HarvestIconISMC->AddInstance(IconTransform); FMassEntityHandle ThisEntity = Context.GetEntity(EntityIt); Context.Defer().RemoveTag(ThisEntity); Context.Defer().AddTag(ThisEntity); } }); } //----------------------------------------------------------------------------- // ALWFragmentTestFarmPlot //----------------------------------------------------------------------------- AMassEntityTestFarmPlot::AMassEntityTestFarmPlot() : SharedEntityManager(MakeShareable(new FMassEntityManager(this))) { PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bStartWithTickEnabled = true; USceneComponent* SceneComponent = CreateDefaultSubobject(TEXT("SceneComp")); RootComponent = SceneComponent; HarvestIconISMC = CreateDefaultSubobject(TEXT("HarvestIconISMC")); HarvestIconISMC->SetupAttachment(SceneComponent); HarvestIconISMC->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); } void AMassEntityTestFarmPlot::AddItemToGrid(FMassEntityManager& InEntityManager, uint16 X, uint16 Y, FMassArchetypeHandle Archetype, uint16 VisualIndex) { FMassEntityHandle NewItem = InEntityManager.CreateEntity(Archetype); PlantedSquares[X + Y * GridWidth] = NewItem; InEntityManager.GetFragmentDataChecked(NewItem).DeltaWaterPerSecond = FMath::FRandRange(-0.01f, -0.001f); InEntityManager.GetFragmentDataChecked(NewItem).NumSecondsLeft = 5 + (FMath::Rand() % 100); FFarmGridCellData GridCoords; GridCoords.CellX = X; GridCoords.CellY = Y; InEntityManager.GetFragmentDataChecked(NewItem) = GridCoords; const FVector MeshPosition(X*GridCellWidth, Y*GridCellHeight, 0.0f); const FRotator MeshRotation(0.0f, FMath::FRand()*360.0f, 0.0f); const FVector MeshScale(1.0f, 1.0f, 1.0f); //@TODO: plumb in scale param? const FTransform MeshTransform(MeshRotation, MeshPosition, MeshScale); FFarmVisualFragment& VisualComp = InEntityManager.GetFragmentDataChecked(NewItem); VisualComp.VisualType = VisualIndex; VisualComp.InstanceIndex = VisualDataISMCs[VisualComp.VisualType]->AddInstance(MeshTransform); } void AMassEntityTestFarmPlot::BeginPlay() { Super::BeginPlay(); if (TestDataCropIndicies.Num() == 0 || TestDataFlowerIndicies.Num() == 0 || VisualDataTable.Num() == 0) { UE_LOG(LogMass, Error, TEXT("%s is misconfigured. Make sure TestDataCropIndicies, TestDataFlowerIndicies and VisualDataTable are not empty"), *GetName()); return; } FMassEntityManager& EntityManager = SharedEntityManager.Get(); EntityManager.Initialize(); FMassArchetypeHandle CropArchetype = EntityManager.CreateArchetype(TArray{ FFarmWaterFragment::StaticStruct(), FFarmCropFragment::StaticStruct(), FHarvestTimerFragment::StaticStruct(), FFarmVisualFragment::StaticStruct(), FFarmGridCellData::StaticStruct() }); FMassArchetypeHandle FlowerArchetype = EntityManager.CreateArchetype(TArray{ FFarmWaterFragment::StaticStruct(), FFarmFlowerFragment::StaticStruct(), FHarvestTimerFragment::StaticStruct(), FFarmVisualFragment::StaticStruct(), FFarmGridCellData::StaticStruct() }); PerFrameSystems.Add(NewObject(this)); PerSecondSystems.Add(NewObject(this)); PerSecondSystems.Add(NewObject(this)); PerSecondSystems.Add(NewObject(this)); UFarmHarvestTimerSetIcon* IconSetter = NewObject(this); IconSetter->HarvestIconISMC = HarvestIconISMC; IconSetter->GridCellWidth = GridCellWidth; IconSetter->GridCellHeight = GridCellHeight; IconSetter->HarvestIconHeight = 200.0f; IconSetter->HarvestIconScale = HarvestIconScale; PerSecondSystems.Add(IconSetter); HarvestIconISMC->SetCullDistances(IconNearCullDistance, IconFarCullDistance); // Create ISMCs for each mesh type for (const FFarmVisualDataRow& VisualData : VisualDataTable) { UHierarchicalInstancedStaticMeshComponent* HISMC = NewObject(this); HISMC->SetStaticMesh(VisualData.Mesh); if (VisualData.MaterialOverride != nullptr) { HISMC->SetMaterial(0, VisualData.MaterialOverride); } HISMC->SetCullDistances(VisualNearCullDistance, VisualFarCullDistance); HISMC->SetupAttachment(RootComponent); HISMC->RegisterComponent(); VisualDataISMCs.Add(HISMC); } // Plant us a grid const int32 NumGridCells = GridWidth * GridHeight; PlantedSquares.AddDefaulted(NumGridCells); for (uint16 Y = 0; Y < GridHeight; ++Y) { for (uint16 X = 0; X < GridWidth; ++X) { const bool bSpawnCrop = FMath::FRand() < 0.5f; const uint16 VisualIndex = bSpawnCrop ? TestDataCropIndicies[FMath::RandRange(0, TestDataCropIndicies.Num() - 1)] : TestDataFlowerIndicies[FMath::RandRange(0, TestDataFlowerIndicies.Num() - 1)]; AddItemToGrid(EntityManager, X, Y, bSpawnCrop ? CropArchetype : FlowerArchetype, VisualIndex); } } } void AMassEntityTestFarmPlot::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) { Super::TickActor(DeltaTime, TickType, ThisTickFunction); if (PlantedSquares.Num() == 0) { // not configured properly, ignore. return; } FMassEntityManager& EntityManager = SharedEntityManager.Get(); // Run every frame systems { FMassProcessingContext Context(EntityManager, DeltaTime); UE::Mass::Executor::RunProcessorsView(PerFrameSystems, Context); } // Run per-second systems when it's time NextSecondTimer -= DeltaTime; while (NextSecondTimer < 0.0f) { FMassProcessingContext Context(EntityManager, 1.f); UE::Mass::Executor::RunProcessorsView(PerSecondSystems, Context); NextSecondTimer += 1.0f; } }