├── .gitignore ├── LICENSE ├── LocusReplicationGraph.uplugin ├── README.md ├── Resources └── Icon128.png └── Source └── LocusReplicationGraph ├── LocusReplicationGraph.Build.cs ├── Private ├── LocusReplicationBPHelpers.cpp ├── LocusReplicationGraph.cpp └── LocusReplicationGraphModule.cpp └── Public ├── LocusReplicationBPHelpers.h ├── LocusReplicationGraph.h └── LocusReplicationGraphModule.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Visual Studio 2015 database file 5 | *.VC.db 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.ipa 36 | 37 | # These project files can be generated by the engine 38 | *.xcodeproj 39 | *.xcworkspace 40 | *.sln 41 | *.suo 42 | *.opensdf 43 | *.sdf 44 | *.VC.db 45 | *.VC.opendb 46 | 47 | # Precompiled Assets 48 | SourceArt/**/*.png 49 | SourceArt/**/*.tga 50 | 51 | # Binary Files 52 | Binaries/* 53 | Plugins/*/Binaries/* 54 | 55 | # Builds 56 | Build/* 57 | 58 | # Whitelist PakBlacklist-.txt files 59 | !Build/*/ 60 | Build/*/** 61 | !Build/*/PakBlacklist*.txt 62 | 63 | # Don't ignore icon files in Build 64 | !Build/**/*.ico 65 | 66 | # Built data for maps 67 | *_BuiltData.uasset 68 | 69 | # Configuration files generated by the Editor 70 | Saved/* 71 | 72 | # Compiled source files for the engine to use 73 | Intermediate/* 74 | Plugins/*/Intermediate/* 75 | 76 | # Cache files for the editor to use 77 | DerivedDataCache/* 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Locus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LocusReplicationGraph.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 1, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Locus Replication Graph Plugin", 6 | "Description": "One of default implementations of Replication Graph, which can be used with blueprint.", 7 | "Category": "Networking", 8 | "CreatedBy": "HashemGameDevForkOfLocus", 9 | "CreatedByURL": "https://www.unrealengine.com/marketplace/en-US/profile/HashemGameDevStore", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "EnabledByDefault": false, 14 | "CanContainContent": false, 15 | "IsBetaVersion": true, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "LocusReplicationGraph", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default" 22 | } 23 | ], 24 | "Plugins": [ 25 | { 26 | "Name": "ReplicationGraph", 27 | "Enabled": true 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hashem Game Dev Edit of Locus Replication Graph 2 | 3 | This fork adds support for 4.26-5.0 which are the latest versions of Unreal engine , Star and share with others. 4 | 5 | This is an extension of ReplicationGraph plugin introduced in UE 4.20. 6 | It contains a few blueprint library functions that controls replication graph. 7 | 8 | As Epic provides a good example of ReplicationGraph in **ShooterGame** project, most of concepts are came from there. 9 | 10 | ## What it does 11 | 12 | Basic functionality that ReplicationGraph provies + 13 | It expose basic control of ReplicationGraph to Blueprints. 14 | It supports actors that only relevant to owner connection. 15 | It supports actors that only relevant to team connections. 16 | It provides api that add/remove dependent actors(c++/blueprint). 17 | 18 | ## How to install 19 | 20 | 1. Download or clone this repo. 21 | 22 | 2. Create "Plugins" folder in root folder of your project. 23 | 24 | 3. UnZip/Paste downloaded files into Plugins/LocusReplicationGraph. 25 | 26 | 4. Open project, under Edit/Plugins window, enable LocusReplicationGraphPlugin in Project/Networking. This step requires restart/compile. 27 | ![enableplugin](https://user-images.githubusercontent.com/6591432/50825525-9a78ef00-137c-11e9-973b-07614cc0a91e.PNG) 28 | 29 | 5. Now, create your blueprint class and inherit from LocusReplicationGraph. Do required settings there. 30 | ![createblueprint](https://user-images.githubusercontent.com/6591432/50824559-410fc080-137a-11e9-8927-e6698219c19f.PNG) 31 | 32 | 6. Open up Config/DefaultEngine.ini then add 33 | ```text 34 | [/Script/OnlineSubsystemUtils.IpNetDriver] 35 | ReplicationDriverClassName="[Your custom blueprint created in step 5]" 36 | ``` 37 | *this usually looks like this 38 | ```text 39 | ReplicationDriverClassName="/Game/Blueprints/Online/CustomReplicationGraph.CustomReplicationGraph_C" 40 | ``` 41 | ![defaultengine](https://user-images.githubusercontent.com/6591432/50824553-3d7c3980-137a-11e9-9e9f-de9bf2808a2f.jpg) 42 | 43 | 7. Play game in network mode and type console command blow to ensure it works 44 | ```text 45 | LocusRepGraph.PrintRouting 46 | ``` 47 | 48 | ## How to use it. 49 | 50 | After installation, open up created blueprint class. 51 | In Class default details, there are some settings that you may customize to fit your own game. 52 | ![classdefaults](https://user-images.githubusercontent.com/6591432/50826680-a914d580-137f-11e9-98d0-5f2a5dae104e.PNG) 53 | Each of these values have simple description you can check when you hover your mouse. 54 | 55 | During play, you can access ReplicationGraph's functionality with given library functions. 56 | ![blueprintfunctionlist](https://user-images.githubusercontent.com/6591432/50826684-ac0fc600-137f-11e9-818d-4f57e5c08b99.PNG) 57 | 58 | 1. **Add/Remove Dependent Actor.** 59 | * Add/Remove Dependent actor to Replicator's dependent actor list. Whenever Replicator actor replicates, DependentActor will replicate either. 60 | * Dependent Actor should not routed to any nodes(but bReplicated=true), as well as Replicator actor should be currently networked. 61 | 62 | 2. **Set Team for Player Controller.** 63 | * Set Team name for a APlayerController. Name_None does not have team(default) 64 | * Routed Policy Relevant Team Connection will show your owned Actors to teammates 65 | 66 | 3. **Change Owner and Refresh Replication.** 67 | * As we don't collect all replicated actor's owner during playtime, you have to tell exactly which actor want to change it's owner. 68 | * Actor will be out of ReplicationRgaph, and back to it after changing owner(inside function) 69 | 70 | 71 | 72 | ## Limitations 73 | 74 | It has same limitations that original replication graph has. 75 | For performance reasons, only initial setup is exposed to blueprints. 76 | Exceptions are setting team, owner, dependent actor. 77 | 78 | This plugin does not support complex owner chain. It only look for it's NetOwner at spawn time. 79 | If you want to change owner after spawn, use provided function. 80 | If A owns B, and B owns C. changing owner should be from bottom to top. C->B->A. 81 | This is for performance reason. I don't want to find and look up all possible actors per a frame. 82 | 83 | ## What the hell is ReplicationGraph 84 | 85 | In Unreal Engine Networking, Listen/Dedicated Server replicates networked actors to connections. To reduce network bandwidth and performance, those actors expected to have relevancy to that connection. In default implementation, only actors which have less distance (from ViewPosition) than NetCullDistance value will be replicated. Of course, some classes such as APlayerController do have their own replication rules. 86 | 87 | Let's say we have 10 replicated(networked) actors, to know it's relevancy to a connection, we have to calculate distance 10 times per connection. Looks straightforward and simple. But what if we need more and more replicated actors and player connections? Let's say we're making a PUBG like game that has 1000+ replicated actors and 100+ connections. In that case, we have to run 1000*100 calculations to get actor relevancies, and that is huge. 88 | 89 | But most of MMO server implementation, there usually is a system AKA **Interest Management**. Which divides entire world to small divisions and populate relevancy only in interested divisions. It is very common optimization in MMOGs. Back to UnrealEngine, Epic also introduced this kind of relevancy optimization in ReplicationGraph which is provided by Engine Plugin(Take a look at UReplicationGraphNode_GridSpatialization2D class). 90 | 91 | A ReplicationGraph is collection of ReplicationGraphNodes. A ReplicationGraphNode is a main building block that calculates actor relevancy for a connection. There are many kind of ReplicationGraphNode that completes ReplicationGraph functionality. AlwaysRelevant Node(AGameState), GridSpatialization2D Node(Actors culled by NetCullDistance), AlwaysRelevantForConnection Node(APlayerController, APlayerState) are one of these. 92 | 93 | ## References 94 | 95 | https://docs.unrealengine.com/en-us/Engine/Networking/ReplicationGraph 96 | https://www.youtube.com/watch?v=CDnNAAzgltw 97 | 98 | Thank you! 99 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/locus84/LocusReplicationGraph/b41ff38c013d1b207c975540c725d09b4e7b8cdd/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/LocusReplicationGraph/LocusReplicationGraph.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | namespace UnrealBuildTool.Rules 4 | { 5 | public class LocusReplicationGraph : ModuleRules 6 | { 7 | public LocusReplicationGraph(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "ReplicationGraph" }); 12 | 13 | PrivateDependencyModuleNames.AddRange(new string[] { }); 14 | 15 | if (Target.bBuildDeveloperTools || (Target.Configuration != UnrealTargetConfiguration.Shipping && Target.Configuration != UnrealTargetConfiguration.Test)) 16 | { 17 | PrivateDependencyModuleNames.Add("GameplayDebugger"); 18 | PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=1"); 19 | } 20 | else 21 | { 22 | PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=0"); 23 | } 24 | 25 | // Uncomment if you are using Slate UI 26 | // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); 27 | 28 | // Uncomment if you are using online features 29 | // PrivateDependencyModuleNames.Add("OnlineSubsystem"); 30 | 31 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Source/LocusReplicationGraph/Private/LocusReplicationBPHelpers.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "LocusReplicationBPHelpers.h" 4 | #include "LocusReplicationGraph.h" 5 | 6 | void ULocusReplicationBPHelpers::SetTeamForPlayerController(APlayerController* Player, FName TeamName) 7 | { 8 | if (ULocusReplicationGraph* LocusGraph = FindLocusReplicationGraph(Player)) 9 | { 10 | LocusGraph->SetTeamForPlayerController(Player, TeamName); 11 | return; 12 | } 13 | 14 | UE_LOG(LogLocusReplicationGraph, Warning, TEXT("LocusReplicationGraph not found")); 15 | } 16 | 17 | void ULocusReplicationBPHelpers::AddDependentActor(AActor* ReplicatorActor, AActor* DependentActor) 18 | { 19 | if (ULocusReplicationGraph* LocusGraph = FindLocusReplicationGraph(ReplicatorActor)) 20 | { 21 | LocusGraph->AddDependentActor(ReplicatorActor, DependentActor); 22 | return; 23 | } 24 | 25 | UE_LOG(LogLocusReplicationGraph, Warning, TEXT("LocusReplicationGraph not found")); 26 | } 27 | 28 | void ULocusReplicationBPHelpers::RemoveDependentActor(AActor* ReplicatorActor, AActor* DependentActor) 29 | { 30 | if (ULocusReplicationGraph* LocusGraph = FindLocusReplicationGraph(ReplicatorActor)) 31 | { 32 | LocusGraph->RemoveDependentActor(ReplicatorActor, DependentActor); 33 | return; 34 | } 35 | 36 | UE_LOG(LogLocusReplicationGraph, Warning, TEXT("LocusReplicationGraph not found")); 37 | } 38 | 39 | void ULocusReplicationBPHelpers::ChangeOwnerAndRefreshReplication(AActor* ActorToChange, AActor* NewOwner) 40 | { 41 | if (ULocusReplicationGraph* LocusGraph = FindLocusReplicationGraph(ActorToChange)) 42 | { 43 | LocusGraph->ChangeOwnerOfAnActor(ActorToChange, NewOwner); 44 | return; 45 | } 46 | 47 | UE_LOG(LogLocusReplicationGraph, Warning, TEXT("LocusReplicationGraph not found")); 48 | } 49 | 50 | ULocusReplicationGraph* ULocusReplicationBPHelpers::FindLocusReplicationGraph(const UObject* WorldContextObject) 51 | { 52 | if (WorldContextObject) 53 | { 54 | if (UWorld* World = WorldContextObject->GetWorld()) 55 | { 56 | if (UNetDriver* NetworkDriver = World->GetNetDriver()) 57 | { 58 | if (ULocusReplicationGraph* LocusGraph = NetworkDriver->GetReplicationDriver()) 59 | { 60 | return LocusGraph; 61 | } 62 | } 63 | } 64 | } 65 | 66 | return nullptr; 67 | } 68 | -------------------------------------------------------------------------------- /Source/LocusReplicationGraph/Private/LocusReplicationGraph.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "LocusReplicationGraph.h" 3 | #include "Engine/LevelScriptActor.h" 4 | #include "Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h" 5 | 6 | #if WITH_GAMEPLAY_DEBUGGER 7 | #include "GameplayDebuggerCategoryReplicator.h" 8 | #endif 9 | 10 | DEFINE_LOG_CATEGORY(LogLocusReplicationGraph); 11 | 12 | 13 | ULocusReplicationGraph::ULocusReplicationGraph() 14 | { 15 | ReplicationConnectionManagerClass = ULocusReplicationConnectionGraph::StaticClass(); 16 | 17 | FClassReplicationInfoPreset PawnClassRepInfo; 18 | PawnClassRepInfo.Class = APawn::StaticClass(); 19 | PawnClassRepInfo.DistancePriorityScale = 1.f; 20 | PawnClassRepInfo.StarvationPriorityScale = 1.f; 21 | PawnClassRepInfo.ActorChannelFrameTimeout = 4; 22 | //small size of cull distance squard leads inconsistant cull becuase of distance between actual character and viewposition 23 | //keep it bigger than distance between actual pawn and inviewer 24 | PawnClassRepInfo.CullDistanceSquared = 15000.f * 15000.f; 25 | ReplicationInfoSettings.Add(PawnClassRepInfo); 26 | } 27 | 28 | void InitClassReplicationInfo(FClassReplicationInfo& Info, UClass* Class, bool bSpatialize, float ServerMaxTickRate) 29 | { 30 | AActor* CDO = Class->GetDefaultObject(); 31 | if (bSpatialize) 32 | { 33 | Info.SetCullDistanceSquared(CDO->NetCullDistanceSquared); 34 | UE_LOG(LogLocusReplicationGraph, Log, TEXT("Setting cull distance for %s to %f (%f)"), *Class->GetName(), Info.GetCullDistanceSquared(), FMath::Sqrt(Info.GetCullDistanceSquared())); 35 | } 36 | 37 | Info.ReplicationPeriodFrame = FMath::Max((uint32)FMath::RoundToFloat(ServerMaxTickRate / CDO->NetUpdateFrequency), 1); 38 | 39 | UClass* NativeClass = Class; 40 | while (!NativeClass->IsNative() && NativeClass->GetSuperClass() && NativeClass->GetSuperClass() != AActor::StaticClass()) 41 | { 42 | NativeClass = NativeClass->GetSuperClass(); 43 | } 44 | 45 | UE_LOG(LogLocusReplicationGraph, Log, TEXT("Setting replication period for %s (%s) to %d frames (%.2f)"), *Class->GetName(), *NativeClass->GetName(), Info.ReplicationPeriodFrame, CDO->NetUpdateFrequency); 46 | } 47 | 48 | const UClass* GetParentNativeClass(const UClass* Class) 49 | { 50 | while (Class && !Class->IsNative()) 51 | { 52 | Class = Class->GetSuperClass(); 53 | } 54 | 55 | return Class; 56 | } 57 | 58 | void ULocusReplicationGraph::InitGlobalActorClassSettings() 59 | { 60 | Super::InitGlobalActorClassSettings(); 61 | 62 | // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 63 | // Programatically build the rules. 64 | // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 65 | 66 | auto AddInfo = [&](UClass* Class, EClassRepNodeMapping Mapping) { ClassRepNodePolicies.Set(Class, Mapping); }; 67 | 68 | AddInfo(AReplicationGraphDebugActor::StaticClass(), EClassRepNodeMapping::NotRouted); // Not needed. Replicated special case inside RepGraph 69 | AddInfo(AInfo::StaticClass(), EClassRepNodeMapping::RelevantAllConnections); // Non spatialized, relevant to all 70 | AddInfo(ALevelScriptActor::StaticClass(), EClassRepNodeMapping::NotRouted); // Not needed 71 | #if WITH_GAMEPLAY_DEBUGGER 72 | AddInfo(AGameplayDebuggerCategoryReplicator::StaticClass(), EClassRepNodeMapping::RelevantOwnerConnection); // Only owner connection viable 73 | #endif 74 | 75 | for (FClassReplicationPolicyPreset PolicyBP : ReplicationPolicySettings) 76 | { 77 | if (PolicyBP.Class) 78 | { 79 | AddInfo(PolicyBP.Class, PolicyBP.Policy); 80 | } 81 | } 82 | 83 | //this does contains all replicated class except GetIsReplicated is false actor 84 | //if someone need to make replication work, mark it as replicated and control it over replication graph 85 | TArray AllReplicatedClasses; 86 | 87 | //Iterate all class 88 | for (TObjectIterator It; It; ++It) 89 | { 90 | UClass* Class = *It; 91 | AActor* ActorCDO = Cast(Class->GetDefaultObject()); 92 | 93 | if (!ActorCDO || !ActorCDO->GetIsReplicated()) 94 | { 95 | continue; 96 | } 97 | 98 | // Skip SKEL and REINST classes. 99 | if (Class->GetName().StartsWith(TEXT("SKEL_")) || Class->GetName().StartsWith(TEXT("REINST_"))) 100 | { 101 | continue; 102 | } 103 | 104 | // -------------------------------------------------------------------- 105 | // This is a replicated class. Save this off for the second pass below 106 | // -------------------------------------------------------------------- 107 | 108 | AllReplicatedClasses.Add(Class); 109 | 110 | // Skip if already in the map (added explicitly) 111 | if (ClassRepNodePolicies.Contains(Class, false)) 112 | { 113 | continue; 114 | } 115 | 116 | auto ShouldSpatialize = [](const AActor* CDO) 117 | { 118 | return CDO->GetIsReplicated() && (!(CDO->bAlwaysRelevant || CDO->bOnlyRelevantToOwner || CDO->bNetUseOwnerRelevancy)); 119 | }; 120 | 121 | auto GetLegacyDebugStr = [](const AActor* CDO) 122 | { 123 | return FString::Printf(TEXT("%s [%d/%d/%d]"), *CDO->GetClass()->GetName(), CDO->bAlwaysRelevant, CDO->bOnlyRelevantToOwner, CDO->bNetUseOwnerRelevancy); 124 | }; 125 | 126 | // Only handle this class if it differs from its super. There is no need to put every child class explicitly in the graph class mapping 127 | UClass* SuperClass = Class->GetSuperClass(); 128 | if (AActor* SuperCDO = Cast(SuperClass->GetDefaultObject())) 129 | { 130 | if (SuperCDO->GetIsReplicated() == ActorCDO->GetIsReplicated() 131 | && SuperCDO->bAlwaysRelevant == ActorCDO->bAlwaysRelevant 132 | && SuperCDO->bOnlyRelevantToOwner == ActorCDO->bOnlyRelevantToOwner 133 | && SuperCDO->bNetUseOwnerRelevancy == ActorCDO->bNetUseOwnerRelevancy 134 | ) 135 | { 136 | //same settings with superclass, ignore this class 137 | continue; 138 | } 139 | } 140 | 141 | if (ShouldSpatialize(ActorCDO)) 142 | { 143 | AddInfo(Class, EClassRepNodeMapping::Spatialize_Dynamic); 144 | } 145 | else if (ActorCDO->bAlwaysRelevant && !ActorCDO->bOnlyRelevantToOwner) 146 | { 147 | AddInfo(Class, EClassRepNodeMapping::RelevantAllConnections); 148 | } 149 | else if (ActorCDO->bOnlyRelevantToOwner) 150 | { 151 | AddInfo(Class, EClassRepNodeMapping::RelevantOwnerConnection); 152 | } 153 | 154 | //TODO:: currently missing feature, !bAlwaysRelevant && bOnlyRelevantToOwner -> only owner see this but is spatialized 155 | } 156 | 157 | TArray ValidClassReplicationInfoPreset; 158 | //custom setting 159 | for (FClassReplicationInfoPreset& ReplicationInfoBP : ReplicationInfoSettings) 160 | { 161 | if (ReplicationInfoBP.Class) 162 | { 163 | GlobalActorReplicationInfoMap.SetClassInfo(ReplicationInfoBP.Class, ReplicationInfoBP.CreateClassReplicationInfo()); 164 | ValidClassReplicationInfoPreset.Add(ReplicationInfoBP); 165 | } 166 | } 167 | 168 | UReplicationGraphNode_ActorListFrequencyBuckets::DefaultSettings.ListSize = 12; 169 | 170 | // Set FClassReplicationInfo based on legacy settings from all replicated classes 171 | for (UClass* ReplicatedClass : AllReplicatedClasses) 172 | { 173 | if (FClassReplicationInfoPreset* Preset = ValidClassReplicationInfoPreset.FindByPredicate([&](const FClassReplicationInfoPreset& Info) { return ReplicatedClass->IsChildOf(Info.Class.Get()); })) 174 | { 175 | //duplicated or set included child will be ignored 176 | if (Preset->Class.Get() == ReplicatedClass || Preset->IncludeChildClasses) 177 | { 178 | continue; 179 | } 180 | } 181 | 182 | const bool bClassIsSpatialized = IsSpatialized(ClassRepNodePolicies.GetChecked(ReplicatedClass)); 183 | 184 | FClassReplicationInfo ClassInfo; 185 | InitClassReplicationInfo(ClassInfo, ReplicatedClass, bClassIsSpatialized, NetDriver->NetServerMaxTickRate); 186 | GlobalActorReplicationInfoMap.SetClassInfo(ReplicatedClass, ClassInfo); 187 | } 188 | 189 | 190 | // Print out what we came up with 191 | UE_LOG(LogLocusReplicationGraph, Log, TEXT("")); 192 | UE_LOG(LogLocusReplicationGraph, Log, TEXT("Class Routing Map: ")); 193 | UEnum* Enum = FindObject(ANY_PACKAGE, TEXT("EClassRepNodeMapping")); 194 | for (auto ClassMapIt = ClassRepNodePolicies.CreateIterator(); ClassMapIt; ++ClassMapIt) 195 | { 196 | UClass* Class = CastChecked(ClassMapIt.Key().ResolveObjectPtr()); 197 | const EClassRepNodeMapping Mapping = ClassMapIt.Value(); 198 | 199 | // Only print if different than native class 200 | UClass* ParentNativeClass = GetParentNativeClass(Class); 201 | const EClassRepNodeMapping* ParentMapping = ClassRepNodePolicies.Get(ParentNativeClass); 202 | if (ParentMapping && Class != ParentNativeClass && Mapping == *ParentMapping) 203 | { 204 | continue; 205 | } 206 | UE_LOG(LogLocusReplicationGraph, Log, TEXT(" %s (%s) -> %s"), *Class->GetName(), *GetNameSafe(ParentNativeClass), *Enum->GetNameStringByValue(static_cast(Mapping))); 207 | } 208 | 209 | UE_LOG(LogLocusReplicationGraph, Log, TEXT("")); 210 | UE_LOG(LogLocusReplicationGraph, Log, TEXT("Class Settings Map: ")); 211 | FClassReplicationInfo LocusValues; 212 | for (auto ClassRepInfoIt = GlobalActorReplicationInfoMap.CreateClassMapIterator(); ClassRepInfoIt; ++ClassRepInfoIt) 213 | { 214 | const UClass* Class = CastChecked(ClassRepInfoIt.Key().ResolveObjectPtr()); 215 | const FClassReplicationInfo& ClassInfo = ClassRepInfoIt.Value(); 216 | UE_LOG(LogLocusReplicationGraph, Log, TEXT(" %s (%s) -> %s"), *Class->GetName(), *GetNameSafe(GetParentNativeClass(Class)), *ClassInfo.BuildDebugStringDelta()); 217 | } 218 | 219 | 220 | // Rep destruct infos based on CVar value 221 | DestructInfoMaxDistanceSquared = DestructionInfoMaxDistance * DestructionInfoMaxDistance; 222 | 223 | #if WITH_GAMEPLAY_DEBUGGER 224 | AGameplayDebuggerCategoryReplicator::NotifyDebuggerOwnerChange.AddUObject(this, &ULocusReplicationGraph::OnGameplayDebuggerOwnerChange); 225 | #endif 226 | } 227 | 228 | void ULocusReplicationGraph::InitGlobalGraphNodes() 229 | { 230 | // Preallocate some replication lists. comment the following 4 lines in case you use UE5+ 231 | PreAllocateRepList(3, 12); 232 | PreAllocateRepList(6, 12); 233 | PreAllocateRepList(128, 64); 234 | PreAllocateRepList(512, 16); 235 | 236 | // ----------------------------------------------- 237 | // Spatial Actors 238 | // ----------------------------------------------- 239 | 240 | GridNode = CreateNewNode(); 241 | GridNode->CellSize = SpacialCellSize; 242 | GridNode->SpatialBias = SpatialBias; 243 | 244 | if (!EnableSpatialRebuilds) 245 | { 246 | GridNode->AddSpatialRebuildBlacklistClass(AActor::StaticClass()); // Disable All spatial rebuilding 247 | } 248 | 249 | AddGlobalGraphNode(GridNode); 250 | 251 | // ----------------------------------------------- 252 | // Always Relevant (to everyone) Actors 253 | // ----------------------------------------------- 254 | AlwaysRelevantNode = CreateNewNode(); 255 | AddGlobalGraphNode(AlwaysRelevantNode); 256 | } 257 | 258 | void ULocusReplicationGraph::InitConnectionGraphNodes(UNetReplicationGraphConnection* RepGraphConnection) 259 | { 260 | Super::InitConnectionGraphNodes(RepGraphConnection); 261 | 262 | //UE_LOG(LogLocusReplicationGraph, Warning, TEXT("InitConnection : %s"), *RepGraphConnection->NetConnection->PlayerController->GetName()); 263 | ULocusReplicationConnectionGraph* LocusConnManager = Cast(RepGraphConnection); 264 | if (!LocusConnManager) 265 | { 266 | UE_LOG(LogLocusReplicationGraph, Warning, TEXT("Unrecognized ConnectionDriver class, Expected ULocusReplicationConnectionGraph")); 267 | } 268 | 269 | LocusConnManager->AlwaysRelevantForConnectionNode = CreateNewNode(); 270 | AddConnectionGraphNode(LocusConnManager->AlwaysRelevantForConnectionNode, RepGraphConnection); 271 | 272 | LocusConnManager->TeamConnectionNode = CreateNewNode(); 273 | AddConnectionGraphNode(LocusConnManager->TeamConnectionNode, RepGraphConnection); 274 | 275 | //don't care about team names as it's initial value is always NAME_None 276 | } 277 | 278 | void ULocusReplicationGraph::OnRemoveConnectionGraphNodes(UNetReplicationGraphConnection* RepGraphConnection) 279 | { 280 | ULocusReplicationConnectionGraph* LocusConnManager = Cast(RepGraphConnection); 281 | if (LocusConnManager) 282 | { 283 | if (LocusConnManager->TeamName != NAME_None) 284 | { 285 | TeamConnectionListMap.RemoveConnectionFromTeam(LocusConnManager->TeamName, LocusConnManager); 286 | } 287 | } 288 | } 289 | 290 | void ULocusReplicationGraph::RemoveClientConnection(UNetConnection* NetConnection) 291 | { 292 | //we completely override super function 293 | 294 | int32 ConnectionId = 0; 295 | bool bFound = false; 296 | 297 | // Remove the RepGraphConnection associated with this NetConnection. Also update ConnectionIds to stay compact. 298 | auto UpdateList = [&](TArray List) 299 | { 300 | for (int32 idx = 0; idx < Connections.Num(); ++idx) 301 | { 302 | UNetReplicationGraphConnection* ConnectionManager = Connections[idx]; 303 | repCheck(ConnectionManager); 304 | 305 | if (ConnectionManager->NetConnection == NetConnection) 306 | { 307 | ensure(!bFound); 308 | //Nofity this to handle something - remove from team list 309 | OnRemoveConnectionGraphNodes(ConnectionManager); 310 | Connections.RemoveAtSwap(idx, 1, false); 311 | bFound = true; 312 | } 313 | else 314 | { 315 | ConnectionManager->ConnectionOrderNum = ConnectionId++; 316 | } 317 | } 318 | }; 319 | 320 | UpdateList(Connections); 321 | UpdateList(PendingConnections); 322 | 323 | if (!bFound) 324 | { 325 | // At least one list should have found the connection 326 | UE_LOG(LogLocusReplicationGraph, Warning, TEXT("UReplicationGraph::RemoveClientConnection could not find connection in Connection (%d) or PendingConnections (%d) lists"), *GetNameSafe(NetConnection), Connections.Num(), PendingConnections.Num()); 327 | } 328 | } 329 | 330 | void ULocusReplicationGraph::RouteAddNetworkActorToNodes(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& GlobalInfo) 331 | { 332 | EClassRepNodeMapping Policy = GetMappingPolicy(ActorInfo.Class); 333 | switch (Policy) 334 | { 335 | case EClassRepNodeMapping::NotRouted: 336 | { 337 | break; 338 | } 339 | 340 | case EClassRepNodeMapping::RelevantAllConnections: 341 | { 342 | AlwaysRelevantNode->NotifyAddNetworkActor(ActorInfo); 343 | break; 344 | } 345 | 346 | case EClassRepNodeMapping::RelevantOwnerConnection: 347 | case EClassRepNodeMapping::RelevantTeamConnection: 348 | { 349 | RouteAddNetworkActorToConnectionNodes(Policy, ActorInfo, GlobalInfo); 350 | break; 351 | } 352 | 353 | case EClassRepNodeMapping::Spatialize_Static: 354 | { 355 | GridNode->AddActor_Static(ActorInfo, GlobalInfo); 356 | break; 357 | } 358 | 359 | case EClassRepNodeMapping::Spatialize_Dynamic: 360 | { 361 | GridNode->AddActor_Dynamic(ActorInfo, GlobalInfo); 362 | break; 363 | } 364 | 365 | case EClassRepNodeMapping::Spatialize_Dormancy: 366 | { 367 | GridNode->AddActor_Dormancy(ActorInfo, GlobalInfo); 368 | break; 369 | } 370 | }; 371 | } 372 | 373 | void ULocusReplicationGraph::RouteRemoveNetworkActorToNodes(const FNewReplicatedActorInfo& ActorInfo) 374 | { 375 | EClassRepNodeMapping Policy = GetMappingPolicy(ActorInfo.Class); 376 | 377 | switch (Policy) 378 | { 379 | case EClassRepNodeMapping::NotRouted: 380 | { 381 | break; 382 | } 383 | 384 | case EClassRepNodeMapping::RelevantAllConnections: 385 | { 386 | AlwaysRelevantNode->NotifyRemoveNetworkActor(ActorInfo); 387 | break; 388 | } 389 | 390 | case EClassRepNodeMapping::RelevantOwnerConnection: 391 | case EClassRepNodeMapping::RelevantTeamConnection: 392 | { 393 | RouteRemoveNetworkActorToConnectionNodes(Policy, ActorInfo); 394 | break; 395 | } 396 | 397 | case EClassRepNodeMapping::Spatialize_Static: 398 | { 399 | GridNode->RemoveActor_Static(ActorInfo); 400 | break; 401 | } 402 | 403 | case EClassRepNodeMapping::Spatialize_Dynamic: 404 | { 405 | GridNode->RemoveActor_Dynamic(ActorInfo); 406 | break; 407 | } 408 | 409 | case EClassRepNodeMapping::Spatialize_Dormancy: 410 | { 411 | GridNode->RemoveActor_Dormancy(ActorInfo); 412 | break; 413 | } 414 | }; 415 | } 416 | 417 | //this function will be called seamless map transition 418 | //as all actors will be removed in silly order, we have to deal with it 419 | void ULocusReplicationGraph::ResetGameWorldState() 420 | { 421 | Super::ResetGameWorldState(); 422 | 423 | //all actor will be destroyed. just reset it. 424 | PendingConnectionActors.Reset(); 425 | PendingTeamRequests.Reset(); 426 | #pragma warning(push) 427 | #pragma warning(disable: 4458) 428 | auto EmptyConnectionNode = [](TArray& Connections) 429 | { 430 | for (UNetReplicationGraphConnection* ConnManager : Connections) 431 | { 432 | if (ULocusReplicationConnectionGraph* LocusConnManager = Cast(ConnManager)) 433 | { 434 | LocusConnManager->AlwaysRelevantForConnectionNode->NotifyResetAllNetworkActors(); 435 | } 436 | } 437 | }; 438 | #pragma warning(pop) 439 | EmptyConnectionNode(PendingConnections); 440 | EmptyConnectionNode(Connections); 441 | 442 | //as connection does not destroyed, we keep it 443 | //TeamConnectionListMap.Reset(); 444 | } 445 | 446 | // Since we listen to global (static) events, we need to watch out for cross world broadcasts (PIE) 447 | #if WITH_EDITOR 448 | #define CHECK_WORLDS(X) if(X->GetWorld() != GetWorld()) return; 449 | #else 450 | #define CHECK_WORLDS(X) 451 | #endif 452 | 453 | void ULocusReplicationGraph::AddDependentActor(AActor* ReplicatorActor, AActor* DependentActor) 454 | { 455 | if (ReplicatorActor && DependentActor) 456 | { 457 | CHECK_WORLDS(ReplicatorActor); 458 | 459 | if (FGlobalActorReplicationInfo* ReplicationInfo = GlobalActorReplicationInfoMap.Find(ReplicatorActor)) 460 | { 461 | if (!ReplicationInfo->GetDependentActorList().Contains(DependentActor)) 462 | { 463 | GlobalActorReplicationInfoMap.AddDependentActor(ReplicatorActor,DependentActor); 464 | } 465 | } 466 | else 467 | { 468 | UE_LOG(LogLocusReplicationGraph, Warning, TEXT("ReplicatorActor privided is not replicating")); 469 | } 470 | } 471 | } 472 | 473 | void ULocusReplicationGraph::RemoveDependentActor(AActor* ReplicatorActor, AActor* DependentActor) 474 | { 475 | if (ReplicatorActor && DependentActor) 476 | { 477 | CHECK_WORLDS(ReplicatorActor); 478 | 479 | if (FGlobalActorReplicationInfo* ReplicationInfo = GlobalActorReplicationInfoMap.Find(ReplicatorActor)) 480 | { 481 | GlobalActorReplicationInfoMap.RemoveDependentActor(ReplicatorActor,DependentActor); 482 | } 483 | else 484 | { 485 | UE_LOG(LogLocusReplicationGraph, Warning, TEXT("ReplicatorActor privided is not replicating")); 486 | } 487 | } 488 | } 489 | 490 | void ULocusReplicationGraph::ChangeOwnerOfAnActor(AActor* ActorToChange, AActor* NewOwner) 491 | { 492 | EClassRepNodeMapping Policy = GetMappingPolicy(ActorToChange->GetClass()); 493 | if (!ActorToChange || Policy == EClassRepNodeMapping::NotRouted || IsSpatialized(Policy)) 494 | { 495 | //Policy doesn't matter for chaning owner 496 | return; 497 | } 498 | 499 | //remove from previous connection specific nodes. 500 | RouteRemoveNetworkActorToConnectionNodes(Policy, FNewReplicatedActorInfo(ActorToChange)); 501 | 502 | //change owner safely 503 | ActorToChange->SetOwner(NewOwner); 504 | 505 | //re-route to connection specific nodes with new owner 506 | FGlobalActorReplicationInfo& GlobalInfo = GlobalActorReplicationInfoMap.Get(ActorToChange); 507 | RouteAddNetworkActorToConnectionNodes(Policy, FNewReplicatedActorInfo(ActorToChange), GlobalInfo); 508 | } 509 | 510 | void ULocusReplicationGraph::SetTeamForPlayerController(APlayerController* PlayerController, FName NextTeam) 511 | { 512 | if (PlayerController) 513 | { 514 | if (ULocusReplicationConnectionGraph* ConnManager = FindLocusConnectionGraph(PlayerController)) 515 | { 516 | FName CurrentTeam = ConnManager->TeamName; 517 | if (CurrentTeam != NextTeam) 518 | { 519 | if (CurrentTeam != NAME_None) 520 | { 521 | TeamConnectionListMap.RemoveConnectionFromTeam(CurrentTeam, ConnManager); 522 | } 523 | 524 | if (NextTeam != NAME_None) 525 | { 526 | TeamConnectionListMap.AddConnectionToTeam(NextTeam, ConnManager); 527 | } 528 | ConnManager->TeamName = NextTeam; 529 | } 530 | } 531 | else 532 | { 533 | PendingTeamRequests.Emplace(NextTeam, PlayerController); 534 | } 535 | } 536 | } 537 | 538 | void ULocusReplicationGraph::RouteAddNetworkActorToConnectionNodes(EClassRepNodeMapping Policy, const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& GlobalInfo) 539 | { 540 | if (ULocusReplicationConnectionGraph* ConnManager = FindLocusConnectionGraph(ActorInfo.GetActor())) 541 | { 542 | switch (Policy) 543 | { 544 | case EClassRepNodeMapping::RelevantOwnerConnection: 545 | { 546 | ConnManager->AlwaysRelevantForConnectionNode->NotifyAddNetworkActor(ActorInfo); 547 | break; 548 | } 549 | case EClassRepNodeMapping::RelevantTeamConnection: 550 | { 551 | ConnManager->TeamConnectionNode->NotifyAddNetworkActor(ActorInfo); 552 | break; 553 | } 554 | }; 555 | } 556 | else if(ActorInfo.Actor->GetNetOwner()) 557 | { 558 | //this actor is not yet ready. add to pending array to handle pending route 559 | PendingConnectionActors.Add(ActorInfo.GetActor()); 560 | } 561 | } 562 | 563 | 564 | void ULocusReplicationGraph::RouteRemoveNetworkActorToConnectionNodes(EClassRepNodeMapping Policy, const FNewReplicatedActorInfo& ActorInfo) 565 | { 566 | if (ULocusReplicationConnectionGraph* ConnManager = FindLocusConnectionGraph(ActorInfo.GetActor())) 567 | { 568 | switch (Policy) 569 | { 570 | case EClassRepNodeMapping::RelevantOwnerConnection: 571 | { 572 | ConnManager->AlwaysRelevantForConnectionNode->NotifyRemoveNetworkActor(ActorInfo); 573 | break; 574 | } 575 | case EClassRepNodeMapping::RelevantTeamConnection: 576 | { 577 | ConnManager->TeamConnectionNode->NotifyRemoveNetworkActor(ActorInfo); 578 | break; 579 | } 580 | }; 581 | } 582 | else if (ActorInfo.Actor->GetNetOwner()) 583 | { 584 | //this actor is not yet ready. but doesn't matter the pending array contains the actor or not 585 | PendingConnectionActors.Remove(ActorInfo.GetActor()); 586 | } 587 | } 588 | 589 | void ULocusReplicationGraph::HandlePendingActorsAndTeamRequests() 590 | { 591 | if(PendingTeamRequests.Num() > 0) 592 | { 593 | TArray TempRequests = MoveTemp(PendingTeamRequests); 594 | 595 | for (FTeamRequest& Request : TempRequests) 596 | { 597 | if (Request.Requestor && Request.Requestor->IsValidLowLevel()) 598 | { 599 | //if failed, it will automatically re-added to pending list 600 | SetTeamForPlayerController(Request.Requestor, Request.TeamName); 601 | } 602 | } 603 | } 604 | 605 | if (PendingConnectionActors.Num() > 0) 606 | { 607 | TArray TempActors = MoveTemp(PendingConnectionActors); 608 | 609 | for (AActor* Actor : TempActors) 610 | { 611 | if (Actor && Actor->IsValidLowLevel()) 612 | { 613 | if (UNetConnection* Connection = Actor->GetNetConnection()) 614 | { 615 | //if failed, it will automatically re-added to pending list 616 | EClassRepNodeMapping Policy = GetMappingPolicy(Actor->GetClass()); 617 | FGlobalActorReplicationInfo& GlobalInfo = GlobalActorReplicationInfoMap.Get(Actor); 618 | RouteAddNetworkActorToConnectionNodes(Policy, FNewReplicatedActorInfo(Actor), GlobalInfo); 619 | } 620 | } 621 | } 622 | } 623 | } 624 | 625 | 626 | class ULocusReplicationConnectionGraph* ULocusReplicationGraph::FindLocusConnectionGraph(const AActor* Actor) 627 | { 628 | if (Actor) 629 | { 630 | if (UNetConnection* NetConnection = Actor->GetNetConnection()) 631 | { 632 | if (ULocusReplicationConnectionGraph* ConnManager = Cast(FindOrAddConnectionManager(NetConnection))) 633 | { 634 | return ConnManager; 635 | } 636 | } 637 | } 638 | return nullptr; 639 | } 640 | 641 | #if WITH_GAMEPLAY_DEBUGGER 642 | void ULocusReplicationGraph::OnGameplayDebuggerOwnerChange(AGameplayDebuggerCategoryReplicator* Debugger, APlayerController* OldOwner) 643 | { 644 | if (ULocusReplicationConnectionGraph* ConnManager = FindLocusConnectionGraph(OldOwner)) 645 | { 646 | FNewReplicatedActorInfo ActorInfo(Debugger); 647 | ConnManager->AlwaysRelevantForConnectionNode->NotifyRemoveNetworkActor(ActorInfo); 648 | } 649 | 650 | if (ULocusReplicationConnectionGraph* ConnManager = FindLocusConnectionGraph(Debugger->GetReplicationOwner())) 651 | { 652 | FNewReplicatedActorInfo ActorInfo(Debugger); 653 | ConnManager->AlwaysRelevantForConnectionNode->NotifyAddNetworkActor(ActorInfo); 654 | } 655 | } 656 | #endif 657 | 658 | void ULocusReplicationGraph::PrintRepNodePolicies() 659 | { 660 | UEnum* Enum = FindObject(ANY_PACKAGE, TEXT("EClassRepNodeMapping")); 661 | if (!Enum) 662 | { 663 | return; 664 | } 665 | 666 | GLog->Logf(TEXT("====================================")); 667 | GLog->Logf(TEXT("Shooter Replication Routing Policies")); 668 | GLog->Logf(TEXT("====================================")); 669 | 670 | for (auto It = ClassRepNodePolicies.CreateIterator(); It; ++It) 671 | { 672 | FObjectKey ObjKey = It.Key(); 673 | 674 | EClassRepNodeMapping Mapping = It.Value(); 675 | 676 | GLog->Logf(TEXT("%-40s --> %s"), *GetNameSafe(ObjKey.ResolveObjectPtr()), *Enum->GetNameStringByValue(static_cast(Mapping))); 677 | } 678 | } 679 | 680 | EClassRepNodeMapping ULocusReplicationGraph::GetMappingPolicy(UClass* Class) 681 | { 682 | EClassRepNodeMapping* PolicyPtr = ClassRepNodePolicies.Get(Class); 683 | EClassRepNodeMapping Policy = PolicyPtr ? *PolicyPtr : EClassRepNodeMapping::NotRouted; 684 | return Policy; 685 | } 686 | 687 | void UReplicationGraphNode_AlwaysRelevant_ForTeam::GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params) 688 | { 689 | ULocusReplicationConnectionGraph* LocusConnManager = Cast(&Params.ConnectionManager); 690 | if (LocusConnManager && LocusConnManager->TeamName != NAME_None) 691 | { 692 | ULocusReplicationGraph* ReplicationGraph = Cast(GetOuter()); 693 | if (TArray* TeamConnections = ReplicationGraph->TeamConnectionListMap.GetConnectionArrayForTeam(LocusConnManager->TeamName)) 694 | { 695 | for (ULocusReplicationConnectionGraph* TeamMember : *TeamConnections) 696 | { 697 | //we call parent 698 | TeamMember->TeamConnectionNode->GatherActorListsForConnectionDefault(Params); 699 | } 700 | } 701 | } 702 | else 703 | { 704 | Super::GatherActorListsForConnection(Params); 705 | } 706 | } 707 | 708 | UReplicationGraphNode_AlwaysRelevant_WithPending::UReplicationGraphNode_AlwaysRelevant_WithPending() 709 | { 710 | bRequiresPrepareForReplicationCall = true; 711 | } 712 | 713 | void UReplicationGraphNode_AlwaysRelevant_WithPending::PrepareForReplication() 714 | { 715 | ULocusReplicationGraph* ReplicationGraph = Cast(GetOuter()); 716 | ReplicationGraph->HandlePendingActorsAndTeamRequests(); 717 | } 718 | 719 | void UReplicationGraphNode_AlwaysRelevant_ForTeam::GatherActorListsForConnectionDefault(const FConnectionGatherActorListParameters& Params) 720 | { 721 | Super::GatherActorListsForConnection(Params); 722 | } 723 | 724 | TArray* FTeamConnectionListMap::GetConnectionArrayForTeam(FName TeamName) 725 | { 726 | return Find(TeamName); 727 | } 728 | 729 | void FTeamConnectionListMap::AddConnectionToTeam(FName TeamName, ULocusReplicationConnectionGraph* ConnManager) 730 | { 731 | TArray& TeamList = FindOrAdd(TeamName); 732 | TeamList.Add(ConnManager); 733 | } 734 | 735 | void FTeamConnectionListMap::RemoveConnectionFromTeam(FName TeamName, ULocusReplicationConnectionGraph* ConnManager) 736 | { 737 | if (TArray* TeamList = Find(TeamName)) 738 | { 739 | TeamList->RemoveSwap(ConnManager); 740 | //remove team if there's noone left 741 | if (TeamList->Num() == 0) 742 | { 743 | Remove(TeamName); 744 | } 745 | } 746 | } 747 | 748 | 749 | //console commands copied from shooter repgraph 750 | // ------------------------------------------------------------------------------ 751 | 752 | FAutoConsoleCommandWithWorldAndArgs ShooterPrintRepNodePoliciesCmd(TEXT("LocusRepGraph.PrintRouting"), TEXT("Prints how actor classes are routed to RepGraph nodes"), 753 | FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& Args, UWorld* World) 754 | { 755 | for (TObjectIterator It; It; ++It) 756 | { 757 | It->PrintRepNodePolicies(); 758 | } 759 | }) 760 | ); 761 | 762 | 763 | FAutoConsoleCommandWithWorldAndArgs ChangeFrequencyBucketsCmd(TEXT("LocusRepGraph.FrequencyBuckets"), TEXT("Resets frequency bucket count."), 764 | FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray< FString >& Args, UWorld* World) 765 | { 766 | int32 Buckets = 1; 767 | if (Args.Num() > 0) 768 | { 769 | LexTryParseString(Buckets, *Args[0]); 770 | } 771 | 772 | UE_LOG(LogLocusReplicationGraph, Display, TEXT("Setting Frequency Buckets to %d"), Buckets); 773 | for (TObjectIterator It; It; ++It) 774 | { 775 | UReplicationGraphNode_ActorListFrequencyBuckets* Node = *It; 776 | Node->SetNonStreamingCollectionSize(Buckets); 777 | } 778 | })); 779 | -------------------------------------------------------------------------------- /Source/LocusReplicationGraph/Private/LocusReplicationGraphModule.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "LocusReplicationGraphModule.h" 4 | 5 | class FLocusReplicationGraphModule : public ILocusReplicationGraphPlugin 6 | { 7 | /** IModuleInterface implementation */ 8 | virtual void StartupModule() override {} 9 | virtual void ShutdownModule() override {} 10 | virtual bool IsGameModule() const override { return true; } 11 | }; 12 | 13 | IMPLEMENT_GAME_MODULE(FLocusReplicationGraphModule, LocusReplicationGraph) 14 | -------------------------------------------------------------------------------- /Source/LocusReplicationGraph/Public/LocusReplicationBPHelpers.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Kismet/BlueprintFunctionLibrary.h" 7 | #include "LocusReplicationBPHelpers.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class LOCUSREPLICATIONGRAPH_API ULocusReplicationBPHelpers : public UBlueprintFunctionLibrary 14 | { 15 | GENERATED_BODY() 16 | public: 17 | 18 | UFUNCTION(BlueprintCallable, Category = "Network") 19 | static void SetTeamForPlayerController(APlayerController* Player, FName TeamName); 20 | 21 | UFUNCTION(BlueprintCallable, Category = "Network") 22 | static void AddDependentActor(AActor* ReplicatorActor, AActor* DependentActor); 23 | 24 | UFUNCTION(BlueprintCallable, Category = "Network") 25 | static void RemoveDependentActor(AActor* ReplicatorActor, AActor* DependentActor); 26 | 27 | UFUNCTION(BlueprintCallable, Category = "Network") 28 | static void ChangeOwnerAndRefreshReplication(AActor* ActorToChange, AActor* NewOwner); 29 | 30 | UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", Category = "Network")) 31 | static class ULocusReplicationGraph* FindLocusReplicationGraph(const UObject* WorldContextObject); 32 | }; 33 | -------------------------------------------------------------------------------- /Source/LocusReplicationGraph/Public/LocusReplicationGraph.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | #include "CoreMinimal.h" 5 | #include "ReplicationGraph.h" 6 | #include "LocusReplicationGraph.generated.h" 7 | 8 | DECLARE_LOG_CATEGORY_EXTERN(LogLocusReplicationGraph, Display, All); 9 | 10 | //class UReplicationGraphNode_GridSpatialization2D; 11 | class AGameplayDebuggerCategoryReplicator; 12 | class ULocusReplicationConnectionGraph; 13 | 14 | // This is the main enum we use to route actors to the right replication node. Each class maps to one enum. 15 | UENUM(BlueprintType) 16 | enum class EClassRepNodeMapping : uint8 17 | { 18 | // Does not route to any node. Special case when you want to control manual way. 19 | NotRouted, 20 | // Routes to an AlwaysRelevantNode 21 | RelevantAllConnections, 22 | // Routes to an AlwaysRelevantNode_ForConnection node 23 | RelevantOwnerConnection, 24 | // Routes to an AlwaysRelevantNode_ForTeam node 25 | RelevantTeamConnection, 26 | 27 | // ONLY SPATIALIZED Enums below here! See UReplicationGraphBase::IsSpatialized 28 | 29 | // Routes to GridNode: these actors don't move and don't need to be updated every frame. 30 | Spatialize_Static, 31 | // Routes to GridNode: these actors mode frequently and are updated once per frame. 32 | Spatialize_Dynamic, 33 | // Routes to GridNode: While dormant we treat as static. When flushed/not dormant dynamic. Note this is for things that "move while not dormant". 34 | Spatialize_Dormancy, 35 | }; 36 | 37 | 38 | struct FTeamRequest 39 | { 40 | FName TeamName; 41 | APlayerController* Requestor; 42 | FTeamRequest(FName InTeamName, APlayerController* PC):TeamName(InTeamName), Requestor(PC) {} 43 | }; 44 | 45 | 46 | USTRUCT(BlueprintType) 47 | struct LOCUSREPLICATIONGRAPH_API FClassReplicationPolicyPreset 48 | { 49 | GENERATED_BODY() 50 | public: 51 | // Class to set replication policy. 52 | UPROPERTY(EditAnywhere) 53 | TSubclassOf Class; 54 | // Policy to set. 55 | UPROPERTY(EditAnywhere) 56 | EClassRepNodeMapping Policy; 57 | }; 58 | 59 | 60 | USTRUCT(BlueprintType) 61 | struct LOCUSREPLICATIONGRAPH_API FClassReplicationInfoPreset 62 | { 63 | GENERATED_BODY() 64 | public: 65 | 66 | // Class of this Replication info is related 67 | UPROPERTY(EditAnywhere) 68 | TSubclassOf Class; 69 | // How much will distance affect to priority 70 | UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) 71 | float DistancePriorityScale = 1.f; 72 | // How much will stavation affect to priority 73 | UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) 74 | float StarvationPriorityScale = 1.f; 75 | // Cull distance that overrides NetCullDistance (Warning : IsNetRelevantFor will not be called in this system) 76 | UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 77 | float CullDistanceSquared = 0.f; 78 | //Server frame count per actual replication 79 | UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 80 | uint8 ReplicationPeriodFrame = 1; 81 | // How long will this actor channel stay alive even after it's being out of relevancy 82 | UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 83 | uint8 ActorChannelFrameTimeout = 4; 84 | // Whether this setting overrides all child classes or not 85 | UPROPERTY(EditAnywhere) 86 | bool IncludeChildClasses = true; 87 | 88 | FClassReplicationInfo CreateClassReplicationInfo() 89 | { 90 | FClassReplicationInfo Info; 91 | Info.DistancePriorityScale = DistancePriorityScale; 92 | Info.StarvationPriorityScale = StarvationPriorityScale; 93 | Info.SetCullDistanceSquared(CullDistanceSquared); 94 | Info.ReplicationPeriodFrame = ReplicationPeriodFrame; 95 | Info.ActorChannelFrameTimeout = ActorChannelFrameTimeout; 96 | return Info; 97 | } 98 | }; 99 | 100 | 101 | struct LOCUSREPLICATIONGRAPH_API FTeamConnectionListMap : public TMap> 102 | { 103 | public: 104 | //Get array of connection managers for gathering actor list 105 | TArray* GetConnectionArrayForTeam(FName TeamName); 106 | 107 | //Add Connection to team, if there's no array, add one. 108 | void AddConnectionToTeam(FName TeamName, ULocusReplicationConnectionGraph* ConnManager); 109 | 110 | //Remove Connection from team, if there's no member of the team after removal, remove array from the map 111 | void RemoveConnectionFromTeam(FName TeamName, ULocusReplicationConnectionGraph* ConnManager); 112 | }; 113 | 114 | 115 | UCLASS() 116 | class LOCUSREPLICATIONGRAPH_API UReplicationGraphNode_AlwaysRelevant_WithPending : public UReplicationGraphNode_ActorList 117 | { 118 | GENERATED_BODY() 119 | 120 | public: 121 | UReplicationGraphNode_AlwaysRelevant_WithPending(); 122 | virtual void PrepareForReplication() override; 123 | }; 124 | 125 | UCLASS() 126 | class LOCUSREPLICATIONGRAPH_API UReplicationGraphNode_AlwaysRelevant_ForTeam : public UReplicationGraphNode_ActorList 127 | { 128 | GENERATED_BODY() 129 | 130 | public: 131 | //Gather up other team member's list 132 | virtual void GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params) override; 133 | 134 | //Function that calls parent ActorList's GatherActorList... 135 | virtual void GatherActorListsForConnectionDefault(const FConnectionGatherActorListParameters& Params); 136 | }; 137 | 138 | //ReplicationConnectionGraph that holds team information and connection specific nodes. 139 | UCLASS() 140 | class LOCUSREPLICATIONGRAPH_API ULocusReplicationConnectionGraph : public UNetReplicationGraphConnection 141 | { 142 | GENERATED_BODY() 143 | 144 | public: 145 | UPROPERTY() 146 | UReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantForConnectionNode; 147 | 148 | UPROPERTY() 149 | class UReplicationGraphNode_AlwaysRelevant_ForTeam* TeamConnectionNode; 150 | 151 | FName TeamName = NAME_None; 152 | }; 153 | /** 154 | * 155 | */ 156 | UCLASS(Blueprintable) 157 | class LOCUSREPLICATIONGRAPH_API ULocusReplicationGraph : public UReplicationGraph 158 | { 159 | GENERATED_BODY() 160 | 161 | public: 162 | 163 | // How far destruction infos will be sent. When server destroyed an actor that is NetLoadOnClient tick is on 164 | UPROPERTY(EditDefaultsOnly, meta = (ClampMin = "1000.0", ClampMax = "100000.0", UIMin = "1000.0", UIMax = "100000.0")) 165 | float DestructionInfoMaxDistance = 30000.f; 166 | 167 | // Cell size of spatial gird. 168 | UPROPERTY(EditDefaultsOnly, meta = (ClampMin = "1000.0", ClampMax = "100000.0", UIMin = "1000.0", UIMax = "100000.0")) 169 | float SpacialCellSize = 10000.f; 170 | 171 | // Spatial grid out of bound bias. Better not change this. 172 | UPROPERTY(EditDefaultsOnly) 173 | FVector2D SpatialBias = FVector2D(-150000.f, -200000.f); 174 | 175 | // Should spatial grid rebuilt upon detecting an actor that is out of bias? 176 | UPROPERTY(EditDefaultsOnly) 177 | bool EnableSpatialRebuilds = false; 178 | 179 | UPROPERTY(EditDefaultsOnly) 180 | TArray ReplicationPolicySettings; 181 | 182 | UPROPERTY(EditDefaultsOnly) 183 | TArray ReplicationInfoSettings; 184 | 185 | public: 186 | 187 | ULocusReplicationGraph(); 188 | 189 | //Set up Enums for each uclass of entire actor 190 | virtual void InitGlobalActorClassSettings() override; 191 | 192 | //initialize global node, like gridnode 193 | virtual void InitGlobalGraphNodes() override; 194 | 195 | //initialize per connection node, like always relevant node 196 | virtual void InitConnectionGraphNodes(UNetReplicationGraphConnection* RepGraphConnection) override; 197 | //deinitialize per connection node 198 | virtual void OnRemoveConnectionGraphNodes(UNetReplicationGraphConnection* RepGraphConnection); 199 | 200 | //override to make notification when a connection manager is removed 201 | virtual void RemoveClientConnection(UNetConnection* NetConnection) override; 202 | 203 | //routng actor add/removal 204 | virtual void RouteAddNetworkActorToNodes(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& GlobalInfo) override; 205 | virtual void RouteRemoveNetworkActorToNodes(const FNewReplicatedActorInfo& ActorInfo) override; 206 | 207 | virtual void ResetGameWorldState() override; 208 | 209 | //gridnode for spatialization handling 210 | UPROPERTY() 211 | UReplicationGraphNode_GridSpatialization2D* GridNode; 212 | 213 | //always relevant for all connection 214 | UPROPERTY() 215 | UReplicationGraphNode_AlwaysRelevant_WithPending* AlwaysRelevantNode; 216 | 217 | //always relevant for all connection but in streaming level, so always relevant to connection who loaded key level 218 | //TMap AlwaysRelevantStreamingLevelActors; //but this is not needed as AlwaysRelevantNode already handle streaming level 219 | 220 | //Add Dependent Actor to ReplicationActor's Dep List, DependentActor will relevant according to Replicator's relevancy 221 | void AddDependentActor(AActor* ReplicatorActor, AActor* DependentActor); 222 | void RemoveDependentActor(AActor* ReplicatorActor, AActor* DependentActor); 223 | 224 | //Change Owner of an actor that is relevant to connection specific 225 | void ChangeOwnerOfAnActor(AActor* ActorToChange, AActor* NewOwner); 226 | 227 | //SetTeam via Name 228 | void SetTeamForPlayerController(APlayerController* PlayerController, FName TeamName); 229 | 230 | //to handle actors that has no connection at addnofity execution 231 | void RouteAddNetworkActorToConnectionNodes(EClassRepNodeMapping Policy, const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& GlobalInfo); 232 | void RouteRemoveNetworkActorToConnectionNodes(EClassRepNodeMapping Policy, const FNewReplicatedActorInfo& ActorInfo); 233 | 234 | //handle pending team requests and notifies 235 | void HandlePendingActorsAndTeamRequests(); 236 | 237 | ULocusReplicationConnectionGraph* FindLocusConnectionGraph(const AActor* Actor); 238 | 239 | //Just copy-pasted from ShooterGame 240 | #if WITH_GAMEPLAY_DEBUGGER 241 | void OnGameplayDebuggerOwnerChange(AGameplayDebuggerCategoryReplicator* Debugger, APlayerController* OldOwner); 242 | #endif 243 | 244 | void PrintRepNodePolicies(); 245 | 246 | private: 247 | 248 | EClassRepNodeMapping GetMappingPolicy(UClass* Class); 249 | 250 | bool IsSpatialized(EClassRepNodeMapping Mapping) const { return Mapping >= EClassRepNodeMapping::Spatialize_Static; } 251 | 252 | TClassMap ClassRepNodePolicies; 253 | 254 | friend UReplicationGraphNode_AlwaysRelevant_ForTeam; 255 | FTeamConnectionListMap TeamConnectionListMap; 256 | 257 | TArray PendingConnectionActors; 258 | TArray PendingTeamRequests; 259 | 260 | }; 261 | -------------------------------------------------------------------------------- /Source/LocusReplicationGraph/Public/LocusReplicationGraphModule.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleInterface.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | /** 9 | * The public interface to this module 10 | */ 11 | class ILocusReplicationGraphPlugin : public IModuleInterface 12 | { 13 | 14 | public: 15 | 16 | static inline ILocusReplicationGraphPlugin& Get() 17 | { 18 | return FModuleManager::LoadModuleChecked< ILocusReplicationGraphPlugin >("LocusReplicationGraph"); 19 | } 20 | 21 | static inline bool IsAvailable() 22 | { 23 | return FModuleManager::Get().IsModuleLoaded("LocusReplicationGraph"); 24 | } 25 | }; 26 | --------------------------------------------------------------------------------