diff --git a/Content/MassSample/Maps/ISMPerInstanceData.umap b/Content/MassSample/Maps/ISMPerInstanceData.umap new file mode 100644 index 00000000..2394b90a --- /dev/null +++ b/Content/MassSample/Maps/ISMPerInstanceData.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bb2389d734b3f39d6d147c808fc66b5ecbb5c3c97a3be5f743681494a569a44 +size 36727 diff --git a/Plugins/LODExample/Content/BP_ActorVisualization.uasset b/Plugins/LODExample/Content/BP_ActorVisualization.uasset new file mode 100644 index 00000000..d9037b81 --- /dev/null +++ b/Plugins/LODExample/Content/BP_ActorVisualization.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b42ca714085c710d17ede4c086aefcc7382139bc4796bccfa6967b032ac0aac +size 29395 diff --git a/Plugins/LODExample/Content/DA_LODExample.uasset b/Plugins/LODExample/Content/DA_LODExample.uasset new file mode 100644 index 00000000..4fe08f80 --- /dev/null +++ b/Plugins/LODExample/Content/DA_LODExample.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7386535038fd1cca96f55d90b83d8592eef8432e1d636ec7b1f1d9553d1dc421 +size 5969 diff --git a/Plugins/LODExample/Content/EQ_LODExample.uasset b/Plugins/LODExample/Content/EQ_LODExample.uasset new file mode 100644 index 00000000..c6e0630d --- /dev/null +++ b/Plugins/LODExample/Content/EQ_LODExample.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8a9904e01b561c02034c282aa5127c955efc5d3e508f2a7ad9757e564ab204d +size 5607 diff --git a/Plugins/LODExample/Content/LODExampleMap.umap b/Plugins/LODExample/Content/LODExampleMap.umap new file mode 100644 index 00000000..69cc9cb9 --- /dev/null +++ b/Plugins/LODExample/Content/LODExampleMap.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d073333ece012d9899b2c9253ff51becacdb035b061d8069868256dad8b36ec +size 36338 diff --git a/Plugins/LODExample/LODExample.uplugin b/Plugins/LODExample/LODExample.uplugin new file mode 100644 index 00000000..10f66381 --- /dev/null +++ b/Plugins/LODExample/LODExample.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "LODExample", + "Description": "Simple example showcasing LODs for Visualization and Processor logic", + "Category": "Other", + "CreatedBy": "JiRath", + "CreatedByURL": "https://github.com/Ji-Rath", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "LODExample", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Plugins/LODExample/Resources/Icon128.png b/Plugins/LODExample/Resources/Icon128.png new file mode 100644 index 00000000..26245f6a --- /dev/null +++ b/Plugins/LODExample/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7239efaeefbd82de33ebe18518e50de075ea4188a468a9e4991396433d2275f +size 12699 diff --git a/Plugins/LODExample/Source/LODExample/LODExample.Build.cs b/Plugins/LODExample/Source/LODExample/LODExample.Build.cs new file mode 100644 index 00000000..ccdc0d5e --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/LODExample.Build.cs @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class LODExample : ModuleRules +{ + public LODExample(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", "MassLOD", "MassEntity", "MassCommon" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Plugins/LODExample/Source/LODExample/Private/LODExample.cpp b/Plugins/LODExample/Source/LODExample/Private/LODExample.cpp new file mode 100644 index 00000000..185aa97c --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/Private/LODExample.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "LODExample.h" + +#define LOCTEXT_NAMESPACE "FLODExampleModule" + +void FLODExampleModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FLODExampleModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FLODExampleModule, LODExample) \ No newline at end of file diff --git a/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp b/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp new file mode 100644 index 00000000..39688bc5 --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp @@ -0,0 +1,82 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "LODExampleProcessors.h" + +#include "MassCommonFragments.h" +#include "MassLODFragments.h" +#include "MassSimulationLOD.h" + +ULODCollectorExampleProcessor::ULODCollectorExampleProcessor() +{ + bAutoRegisterWithProcessingPhases = true; +} + +ULODExampleProcessor::ULODExampleProcessor() +{ + ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Tasks; + ExecutionOrder.ExecuteAfter.Add(UE::Mass::ProcessorGroupNames::LOD); +} + +void ULODExampleProcessor::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); +} + +void ULODExampleProcessor::ConfigureQueries() +{ + EntityQueryBase.AddRequirement(EMassFragmentAccess::ReadOnly); + + // Dont perform logic if outside view + // @note CulledByFrustumTag appears to only be added when not affected by LOD Max Count + EntityQueryBase.AddTagRequirement(EMassFragmentPresence::None); + + //Chunk fragments to tick at specified intervals + EntityQueryBase.AddChunkRequirement(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional); + EntityQueryBase.SetChunkFilter(&FMassSimulationVariableTickChunkFragment::ShouldTickChunkThisFrame); + + // Add base queries to high/med/low entity queries + EntityQuery_High = EntityQueryBase; + EntityQuery_Medium = EntityQueryBase; + EntityQuery_Low = EntityQueryBase; + + EntityQuery_High.AddTagRequirement(EMassFragmentPresence::All); // Query for high LOD + EntityQuery_Medium.AddTagRequirement(EMassFragmentPresence::All); // Query for medium LOD + EntityQuery_Low.AddTagRequirement(EMassFragmentPresence::All); // Query for low +} + +void ULODExampleProcessor::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + // High LOD logic + EntityQuery_High.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + { + TConstArrayView Transforms = Context.GetFragmentView(); + for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++) + { + const FVector& Position = Transforms[EntityIdx].GetTransform().GetLocation(); + DrawDebugPoint(GetWorld(), Position+(FVector::UpVector*500.f), 20.f, FColor::Green, false, 0.25f); + } + }); + + // Med LOD logic + EntityQuery_Medium.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + { + TConstArrayView Transforms = Context.GetFragmentView(); + for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++) + { + const FVector& Position = Transforms[EntityIdx].GetTransform().GetLocation(); + DrawDebugPoint(GetWorld(), Position+(FVector::UpVector*500.f), 20.f, FColor::Yellow, false, 0.25f); + } + }); + + // Low LOD logic + EntityQuery_Low.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + { + TConstArrayView Transforms = Context.GetFragmentView(); + for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++) + { + const FVector& Position = Transforms[EntityIdx].GetTransform().GetLocation(); + DrawDebugPoint(GetWorld(), Position+(FVector::UpVector*500.f), 20.f, FColor::Red, false, 0.25f); + } + }); +} diff --git a/Plugins/LODExample/Source/LODExample/Public/LODExample.h b/Plugins/LODExample/Source/LODExample/Public/LODExample.h new file mode 100644 index 00000000..fc8d2052 --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/Public/LODExample.h @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FLODExampleModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Plugins/LODExample/Source/LODExample/Public/LODExampleProcessors.h b/Plugins/LODExample/Source/LODExample/Public/LODExampleProcessors.h new file mode 100644 index 00000000..fb489c69 --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/Public/LODExampleProcessors.h @@ -0,0 +1,38 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "MassLODCollectorProcessor.h" +#include "LODExampleProcessors.generated.h" + +//MassSample already includes MS + +/** + * Simple class to enable LODCollection for entities + * @see UMassCrowdLODCollectorProcessor + */ +UCLASS() +class LODEXAMPLE_API ULODCollectorExampleProcessor : public UMassLODCollectorProcessor +{ + GENERATED_BODY() + + ULODCollectorExampleProcessor(); +}; + +UCLASS() +class LODEXAMPLE_API ULODExampleProcessor : public UMassProcessor +{ + GENERATED_BODY() + + ULODExampleProcessor(); + + virtual void Initialize(UObject& Owner) override; + virtual void ConfigureQueries() override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQueryBase; + FMassEntityQuery EntityQuery_High; + FMassEntityQuery EntityQuery_Medium; + FMassEntityQuery EntityQuery_Low; +}; diff --git a/Plugins/MassCommunitySample/Source/MassCommunitySample/Common/Fragments/MSFragments.h b/Plugins/MassCommunitySample/Source/MassCommunitySample/Common/Fragments/MSFragments.h index 6474c9bb..5dbbb0d8 100644 --- a/Plugins/MassCommunitySample/Source/MassCommunitySample/Common/Fragments/MSFragments.h +++ b/Plugins/MassCommunitySample/Source/MassCommunitySample/Common/Fragments/MSFragments.h @@ -20,6 +20,12 @@ struct MASSCOMMUNITYSAMPLE_API FSampleColorFragment : public FMassFragment FColor Color = FColor::Red; }; +USTRUCT() +struct FISMPerInstanceDataFragment : public FMassFragment +{ + GENERATED_BODY() + float data = 0; +}; USTRUCT() struct MASSCOMMUNITYSAMPLE_API FInterpLocationFragment : public FMassFragment diff --git a/README.md b/README.md index 7e64131a..015fd257 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,11 @@ After installing the requirements from above, follow these steps: > 4.8 [Observers](#mass-o) >       4.8.1 [Observers limitations](#mass-o-n) >       4.8.2 [Observing multiple Fragment/Tags](#mass-o-mft) -> 4.10 [Mulitthreading](#mass-mt) -> 5. [Mass common operations](#mass-cm) +> 4.10 [Multithreading](#mass-mt) +> 5. [Common Mass operations](#mass-cm) +> 5.1 [Spawning entities](#mass-cm-spae) +> 5.2 [Destroying entities](#mass-cm-dsae) +> 5.3 [Operating Entities](#mass-cm-opee) > 6. [Mass Plugins and Modules](#mass-pm) > 6.1 [MassEntity](#mass-pm-me) > 6.2 [MassGameplay](#mass-pm-gp) @@ -99,7 +102,7 @@ In Mass, some ECS terminology differs from the norm in order to not get confused Typical Unreal Engine game code is expressed as actor objects that inherit from parent classes to change their data and functionality based on what they ***are***. In an ECS, an entity is only composed of fragments that get manipulated by processors based on which ECS components they ***have***. -An entity is really just a small unique identifier that points to some fragments. A Processor defines a query that filters only for entities that have specific fragments. For example, a basic "movement" Processor could query for entities that have a transform and velocity component to add the velocity to their current transform position. +An entity is really just a small unique identifier that points to some fragments. A Processor defines a query that filters only for entities that have specific fragments, and then performs an operation on those fragments. For example, a basic "movement" Processor could query for entities that have a transform and velocity component to add the velocity to their current transform position. Fragments are stored in memory as tightly packed arrays of other identical fragment arrangements called archetypes. Because of this, the aforementioned movement processor can be incredibly high performance because it does a simple operation on a small amount of data all at once. New functionality can easily be added by creating new fragments and processors. @@ -136,6 +139,8 @@ Currently, the sample features the following: ### 4.1 Entities Small unique identifiers that point to a combination of [fragments](#mass-fragments) and [tags](#mass-tags) in memory. Entities are mainly a simple integer ID. For example, entity 103 might point to a single projectile with transform, velocity, and damage data. + + ### 4.2 Fragments Data-only `UScriptStructs` that entities can own and processors can query on. To create a fragment, inherit from [`FMassFragment`](https://docs.unrealengine.com/5.0/en-US/API/Plugins/MassEntity/FMassFragment/). @@ -451,7 +456,7 @@ MyQuery.ForEachEntityChunk(EntitySubsystem, Context, [](FMassExecutionContext& C } }); ``` - + #### 4.6.3 Mutating entities with `Defer()` @@ -869,7 +874,7 @@ Out of the box Mass can spread out work to threads in two different ways: -## 5. Mass common operations +## 5. Common Mass operations This section is designed to serve as a quick reference for how to perform common operations with Mass. As usual, we are open to ideas on how to organize this stuff!! As a rule of thumb, most entity mutations (adding/removing components, spawning or removing entities) are generally done by deferring them from inside of processors. @@ -881,7 +886,7 @@ As a rule of thumb, most entity mutations (adding/removing components, spawning - + ## 5.1 Spawning entities In this Section we are going to review different methods to spawn entities. First, we review the `Mass Spawner`, which is useful to spawn entities with predefined data. Then, we'll move to more complex spawning methods that enable us to have fine grained control over the spawning. @@ -954,29 +959,35 @@ Currently, my best guess is to use `FBuildEntityFromFragmentInstances` and then It is very important to remember that Observers are only triggered explicitely in certain functions out of the box. [Check out the list here.](#mass-o-n) - +[TODO] - -## 5.2 Outside Entities + +## 5.3 Operating Entities + +In this Section we are going to explore the most relevant tools Mass offers to operate Entities. This covers all the get and set operations and structures to work with them (fragment, archetype, tags...). -In cases where we need to access entities outside of the current processing context (e.g. avoiding another crowd entity that this entity is close to) one can call all of the regular Mass Subsystem functions or deferred actions on them. This is not ideal for cache coherency but is nearly unavoidable in gameplay code. +**Note:** In cases where we need to operate with Entities outside the current processing context (e.g. avoidance between Entity crowds) it is possible to call all of the regular Mass Subsystem functions or deferred actions on them. This is not ideal for cache coherency but it is nearly unavoidable in gameplay code. -## 5.2.1 FMassEntityView +## 5.2.1 `FMassEntityView` -`FMassEntityView` is a struct that makes checking for and getting fragments on another entity easier. It is normally constructed with an `FMassEntityHandle` and a `UMassEntitySubsystem`. On construction, the Entity View caches the entity's current archetype data for use later, reducing repeated work needed when normally calling the subsystem to retrieve information about a it multiple times. +`FMassEntityView` is a struct that eases all kinds of Entity operations. It is composed by a `FMassEntityHandle` and a `UMassEntitySubsystem`. On construction, the `FMassEntityView` caches the Entity's archetype data, which will later reduce repeated work needed to retrieve information about the Entity. -It also has functions for retreiving or changing fragment data already on the entity. +Following next, we expose some of the relevant functions of `FMassEntityView`: + +- [TODO] +- [TODO] +- [TODO] -In this example we check for if another entity is an enemy and retrieve specific fragment data if it is. -```c +In the following example, we check if `NearbyEntity` is an enemy, if it is, we damage it: +```c++ FMassEntityView EntityView(EntitySubsystem, NearbyEntity.Entity); if (EntityView.HasTag()) @@ -988,6 +999,7 @@ if (EntityView.HasTag()) } ``` +