#Man, Iris is already pissing me off. So

1 messages · Page 1 of 1 (latest)

hot mist
#
  • Can only have one DynamicFilter per Object.
    • Need different systems to decide on if an Object is filtered or not? Can't really use them, as there can only be one.
    • DynamicFilters also don't give you much information about the Object, and starting to access that in one way or another defeats the purpose of them being mostly cache miss free.
  • ConnectionFilters are meant to used with static information. Probably something that is once allow/disallowed and doesn't really change anymore after that.
  • GroupFilters can only exclude things. While there is an inclusion filter it only works as a counter to DynamicFilters, so that those don't filter out things we still want (like our team mates).
    • Since all they do is exclude, it's enough for one to exclude an Object to not be replicated anymore, even if another one allows it, a lot of conditional logic has to be written to ensure that Objects aren't suddenly gone.
    • Epic says setups where Objects often change between groups should be using DynamicFilters, but since there can only be one per Object, it would be one massive, multiple hundred to thousand lines of code file, basically handling EVERYTHING about the filter of the objects, while not actually providing enough information to do this properly. And let's not forget Epic also says that using them is bad for performance.

What should one use then? No idea. Probably not Iris at all?

#

E.g. a game with two teams and a volume that hides members of the other team if non of your teammates are inside the same volume.

Could make a GroupFilter per volume, but that's a lot of Groups. And moving quickly in and out of Volumes would make adding/removing to and from Groups happen a lot, which Epic is again not happy about.

Even if we do that, we would need to add a lot of conditions around this, otherwise we would end up hiding team mates that walk into the volume.
One most likely would need to allow all connections of team mates whenever one player walks into the volume. And that might work, or it adds a lot of extra problems.

Now what if add a simple "in view" filter to this. Enemies that aren't in view should not be relevant and be filtered. Team mates should not be filtered. And if one team mate sees an enemy, so should all their team mates.

One could make a GroupFilter for "in view", allow all team mate connections for it and add enemies to it whenever they are visible.
That would probably work, but now that will clash with the volume GroupFilter. What if one would want the "in view" stuff to only apply to actors outside the volume, but actors inside the volume are fine to be behind the player? Now the code that handles the "in view" filtering also has to take the whole volume stuff into account.

Unless I'm missing something, I feel like this is all not really battletested...

#

Feels like I would want one "manager" for all groups in this example. One that figures out if team mates are in volumes, if enemies are in view, etc. and then figures out all the "who is in what group and what connections are allowed on those groups" every frame.

#

At which point I could just fall back to IsNetRelevantFor -.-

fiery notch
#

I think @civic mist has been working with it for awhile now.

civic mist
#

So it's not that iris isn't battle tested (it's not, but this isn't why), it's that iris' chief concern is performance and what you want to do is bad for performance ™️
The only reason iris can go as fast as it does is that deciding relevancy, prioritization, and object dirtying is done without touching any game data at all once iris is ready to start sending stuff. Hence the highly optimized filtering and prioritization systems already in there, and the general recommendations not to write your own (because they're really complicated, and you have to be very careful about what data you touch).

Group filters as you said are generally the solution, but I'd also ask why you're trying to filter at all if things are quickly going in and out of relevancy - that kind of quick start-replication stop-replication isn't just bad for iris, it's bad for clients who need to constantly spawn and destroy those actors. It also causes problems because filtering shouldn't need to run super often - it's meant to be a very coarse view of the world because something right on the edge of relevancy shouldn't matter.

It sounds like you're trying to drive gameplay from relevancy and that's a problem - that's really not what relevancy should be used for, and not what iris' filters are designed for. They're meant to remove data that's unnecessary (and won't be necessary in the near future) from a client - if someone can quickly jump in and out of a volume then it doesn't fit this case.

#

If your entire relevancy scheme is based on these volumes (and not based on distance or any other spatial checks) then yes, it would be best to write your own dynamic filter (which epic warns against mostly because there's no docs and it's hard to do it performantly - you'll need to look at the existing spatial filters to see how to do it and oh boy are those dense).

#

If you still need distance-based relevancy then stick with the default spatial filters and figure out a management scheme separate from iris to decide who needs to be included for each player. Or avoid this entirely and don't use relevancy for this task.

hot mist
#

Right, I appreciate the write up. I do know most of that, but I would argue that there are cases where relevancy is important to be accurate.

Competitive Games can't have you be replicated and visible behind walls or inside zones that turn you invisible.

#

Of course it can't be as accurate as "There is a small part of the fabric sticking out behind the wall.", but it should be possible to "cull" Actors based on pre-defined zones, similar to how the DynamicFilter for Grids works.

#

A game like Valorant for example would ensure that enemies that can't possibly be visible to you aren't replicated, so you can't poll the info into some third party app and start cheating. They even have a blogpost about that.

MOBAs generally have systems like "brushes" or "fog of war", that should also be cheat-proof by not replicating information to clients that they aren't supposed to have access to.

#

I get that Iris keeps the information low to stay fast, and that pulling in gameplay information makes it slower, but smaller games with like 10 players on somewhat reasonable map sizes don't need all that fancy optimization most of the time.

#

An alternative would just be very welcome.

civic mist
#

Sure, and in those cases you can use dynamic filters

#

But even something like fog of war in a moba works well enough with an exclusion filter because it's per-team

hot mist
#

Right, but that would be ONE dynamic filter that handles all cases then?

civic mist
#

You want to ideally put the most complex and most general cases into it

#

and then put everything else in filters

#

group filters are fast - you shouldn't have a massive amount of them per-object but you can still mix them with dynamic filters

hot mist
#

I'm mostly stating that due to Objects only being able to have one dynamic filter.

civic mist
#

right, which is why you put the most complex case into it

#

you don't need it to handle absolutely everything - you can use group filters on top of it

hot mist
#

Also, just fyi, the example I wrote in the initial post is totally made up.
But I can imagine being tasked with ensuring that it works like that and was mainly researching how to achieve that with Iris.

#

Not necessarily as naive though.

hot mist
# civic mist right, which is why you put the most complex case into it

So in the case of having to handle the Volume and FOV filtering, even if it's an example that might not come up in real cases, one would write a dynamic one that handles all that together?

I do wonder how I would get the information of an Actor being in a Volume or not this way. I did see RefFlags for properties, but not sure how that works and if that's the way to get properties into the filter.

civic mist
#

I mean the example you wrote is basically another form of spatial filtering - but instead of doing it based on a grid (like the default iris filters do) you do it based on arbitrary volumes. Seems reasonable for a dynamic filter replacing the grid one.

#

FOV filtering is iffy, normally you'd use prioritization for that

#

For something like valorant/csgo visibility filtering I'd probably have a separate subsystem deciding what's visible to each team. Same for fog of war. You could then use normal group filters for that

hot mist
#

I could picture a slow, top down, tactical shooter, where enemies behind covers and walls should not be replicated and woudl generally be invisible, even if the camera angle would show them.

civic mist
#

Right, so you could use per-team group filters for that and have a separate subsystem manage it

#

you don't need a dynamic filter in that case

#

5v5, that's only 2 filters - one per team.

#

If you really want it per-player, 10 filters on each player (or whatever the number would be) is doable but I just don't know how performant it'd be

#

and a dynamic filter is also ofc doable, you'd just make that the main dynamic filter (maybe on top of spatial filtering, but in a small tac shooter that might not even be necessary)

hot mist
#

Hm, how are 2 filters enough for this? Players of TeamA would be in GroupA and TeamB would be in GroupB. After that I can allow connections on the group, where TeamA members are allowed on GroupA and TeamB are allowed on GroupB.
But how does that allow me to filter a single enemy that is behind a wall, while the others aren't?

#

Ah

#

You wrote while I was focused typing

civic mist
#

But how does that allow me to filter a single enemy that is behind a wall, while the others aren't?
with group filters, you simply remove that player from the "visible to other team" filter

#

I guess it'd technically be 4 filters - inclusion filters for allies, exclusion filters for enemies

#

which is perfectly fine

#

we use way more than that

hot mist
#

Lemme think about that for a second.

#

Cause Inclusion Filters can't undo Exclusion Filters

#

Unless you named them that in terms of what they do, and now based on Iris

civic mist
#

you don't have to worry about inclusion not undoing exclusion - you're not using them together. Not every filter has to affect every connection.

#

Having a team in an ally inclusion filter is just to make sure you don't filter them out for dynamic reasons, it's not strictly necessary here.

#

I think you're missing that filters aren't "global" - they affect objects going to a specific set of connections

#

So filtering out everyone on team A for team B means you have an exclusion filter that affects all team B connections and objects of team A

#

that filter does not stop team A from replicating to themselves

hot mist
#

TeamA: Player1, Player2
TeamB :Player3, Player4

**Example: **

Player1 looks at Player3.
Player2 looks at no-one, but passively "sees" Player3 via Player1.
Player3 looks at no-one.
Player4 looks at no-one.

Groups:

VisibleEnemyGroupA:

  • Objects: Player3
  • Connections: Player1, Player2
    VisibleEnemyGroupB:
  • Objects: Empty
  • Connections: Player3, Player4
civic mist
#

yeah, pretty much

hot mist
civic mist
#

inclusion/exclusion filters are somewhat new (and I think renamed) in 5.5

#

they sort of existed pre-5.5, but they got changed in a way I don't remember

hot mist
civic mist
#

no, nothing is filtering them

#

so unless the dynamic filter also filters out player3 they'd still see them

hot mist
#

Player3 is in the VisibleEnemyGroupA and Player4's Connection isn't allowed on that. Wouldn't that filter it?

#

Or is that would you mean with "not global"?

#

Cause adding an exclusion filter sets all connections to disallow on the group filter when adding it :<

civic mist
#

that's what I mean by it not being global. In this example, only player1/2 have 3 filtered. So actually this group setup is wrong.

#

these would be exclusion groups, so you're listing out objects you do not want to see

#

so in this example player1/2 do not see player3

#

and player3/4 don't filter anything

hot mist
#

Oh. I think I've been using them inverted. Which is also why i didn't get why they are called exclusion.

civic mist
#

yeah it's a bit confusing haha

hot mist
#

I saw it as a bucket that everything that is inside can be seen by everything that is set to allow. Which is what it does, but I'm supposed to invert this, right?

civic mist
#

yep

hot mist
#

F

#

TeamA: Player1, Player2
TeamB :Player3, Player4

**Example: **

Player1 looks at Player3.
Player2 looks at no-one, but passively "sees" Player3 via Player1.
Player3 looks at no-one.
Player4 looks at no-one.

Groups:

FilteredEnemyGroupA:

  • Objects: Player4
  • Allowed Connections: Player3, Player4
  • Disallowed Connections: Player1, Player2

FilteredEnemyGroupB:

  • Objects: Player1, Player2
  • Allowed Connections: Player1, Player2
  • Disallowed Connections: Player3, Player4
#

Does that group setup make more sense?

#

If I understand it correctly, this would lead to Player4 being not replicated to Player1 and Player2, cause not visible. And Player3 is not part of the exclusion group, so they are replicated cause they are visible.

#

And since TeamB doesn't see either of their enemies, Player1 and Player2 are inside their exclusion group, while Player1 and Player2's connections are set to allowed so they don't filter themselves?

civic mist
#

There's no allowed/disallowed connections here, there's just connections that have the filter added to them and ones that don't.

#

again, filters aren't global: they only affect connections they are explicitly added to

hot mist
#

That statement confuses me. I can clearly see how Iris sets all connections to disallowed by default when adding the Group as an exclusion group.

// By default we filter out the group members for all connections
GroupInfos[GroupIndex].ConnectionStateIndex = AllocPerObjectInfo();
SetPerObjectInfoFilterStatus(*GetPerObjectInfo(GroupInfos[GroupIndex].ConnectionStateIndex), ENetFilterStatus::Disallow);

#

I can even repro that by creating an unnamed group and adding a character into it, without setting any connection allowed on that group.

#

Character will stop replicating to its owner etc.

#
const UE::Net::FNetObjectGroupHandle GroupHandle = UISIrisFunctionLibrary::CreateGroup(this);
UISIrisFunctionLibrary::AddGroupToFiltering(this, GroupHandle, /** bExclusion */ true);
UISIrisFunctionLibrary::AddActorToGroup(this, this, GroupHandle);

Calling this on AISCharacter::PossessedBy causes the characters to not be replicated (see image: players are just in the floor with the camera where the controller is).

Note: UISIrisFunctionLibrary is just something that I added to not having to write all the GetReplicationSystem code and to not having to check everything for isvalid everywhere.

#

I have to call UISIrisFunctionLibrary::SetGroupFilterStatus(this, GroupHandle, GetNetConnection(), true); on top of that, to ensure that at least the owning player gets their character.

So simply adding the Character to the exclusion group will remove exclude it, unless I first allow all connections on the group.

#

Here with the actual Iris code instead of my functionlib:

UReplicationSystem* const ReplicationSystem = UE::Net::FReplicationSystemUtil::GetReplicationSystem(this);
const UEngineReplicationBridge* const ReplicationBridge = UE::Net::FReplicationSystemUtil::GetActorReplicationBridge(this);

/// 1. Create new Group (no name for this test).
const UE::Net::FNetObjectGroupHandle GroupHandle = ReplicationSystem->CreateGroup(NAME_None);

/// 2. Add as ExclusionGroup. Will cause all Connections to be disallowed by default.
ReplicationSystem->AddExclusionFilterGroup(GroupHandle);

/// 3. Add Character to Group. Will cause it to not replicate to Clients at all, due to defaulted FilterStatus.
const UE::Net::FNetRefHandle ActorNetRefHandle = ReplicationBridge->GetReplicatedRefHandle(this);
    ReplicationSystem->AddToGroup(GroupHandle, ActorNetRefHandle);

/// 4. Allow NetConnection of the Character on the Group. Required for the Character to replicated to at least the owning client.
const uint32 ConnectionId = GetNetConnection()->GetConnectionHandle().GetParentConnectionId();
ReplicationSystem->SetGroupFilterStatus(GroupHandle, ConnectionId, UE::Net::ENetFilterStatus::Allow);
#

Step 1. and 2. can be replaced with creating just one Group per Team, maybe giving it a name so it can be found if needed. Result is the same though.

#

The only thing I could see is that you mean Connection Filters and not Group Filters, since you said that the filter is added to the Connection.

civic mist
#

ah shit yes, sorry - exclusion groups replicate things only to listed connections by default

#

couple of ways to get around that

#

could have a group-per-player which is less efficient but gives you total control.

#

or you could override the defaults - exclusion filters don't have to exclude from all connections by default

hot mist
distant marsh
#

Is Iris starting to have some documentation written for it or? Cause I'm interested in learning it now that it pretty much makes the rep graph useless.

hot mist
# distant marsh Is Iris starting to have some documentation written for it or? Cause I'm interes...

Afaik just the docs you can find. They are slightly outdated but nothing that really stops you from getting started.

The thing is, however, that beyond enabling it, there isn't that much to it.

You have like roughly 3 topics:

  • Prioritizing
  • Filtering
  • Custom Serializer

The third one is something you can stay away from for a while, unless you already have normal ones and need that in Iris. But usually Iris does a good job already.

Prioritizing can either be done with a static value or with a dynamic prioritizer. There you either use an existing one or learn how to write your own. An existing one might again be enough for the start.

And Filtering requires like a handful of straight forward function calls to either allow/disallow connections on an object or a group of objects. As well as, again, a dynamic filter where you can use an existing one or write your own.

Beyond that you probably quickly go down a very technical path, such as writing your own specific bridge or whatever that stuff is all called.
Questionable when exactly this is needed. I would probably handle it as a "crossing that bridge when I get there" topic.

distant marsh
#

Noted, thank you!

hot mist
#

@sterile pawn You can read through this here at some point if you want to go through my pain, especially the last few messages.

sterile pawn
#

yeah the thing is where do you call these in gamecode? to set the group and then to assign say X actor to that group?

hot mist
#

fwiw in some manager.

#

You need an Actor for the ReplicationSystem, but despite that it can be anywhere.

sterile pawn
#

right but say i spawn RegionActor

#

how does it go to that group

#

at what point do i put it in that group

#

do i need to listen for actors being spawned?

hot mist
#

Okay, let's do that somewhat simple. Let's say your manager is your GameMode Actor.

#

In your GameMode, e.g. in InitGame, you create a Group.

#
UReplicationSystem* const ReplicationSystem = UE::Net::FReplicationSystemUtil::GetReplicationSystem(this);
const UEngineReplicationBridge* const ReplicationBridge = UE::Net::FReplicationSystemUtil::GetActorReplicationBridge(this);

/// 1. Create new Group (no name for this test).
const UE::Net::FNetObjectGroupHandle GroupHandle = ReplicationSystem->CreateGroup(NAME_None);

/// 2. Add as ExclusionGroup. Will cause all Connections to be disallowed by default.
ReplicationSystem->AddExclusionFilterGroup(GroupHandle);
#

Something like this.

#

That's now a Group that every connection is disallowed on by default, and it has no Actors in it.

sterile pawn
#

ok

hot mist
#

Now the GroupHandle you can save as a property fwiw, or you give it a proper name.

#

Then, when you spawn your RegionActor, you add it to that group.

sterile pawn
#

and this only has to be on the server right?

hot mist
#

You can do that with a call into the GameMode or just in the RegionActor itself

#

Yeah

sterile pawn
#

right, see the thing is we can spawn these in many places so i kinda wanted it to be a bit more err non dependent on specific call sites

#

for putting it into a group

#

like bp calls SpawnActor node

#

which won't do it

hot mist
#
void AYourGameMode::RegisterRegionPoint(ARegionPoint* RegionPoint)
{
  UReplicationSystem* const ReplicationSystem = UE::Net::FReplicationSystemUtil::GetReplicationSystem(this);
  const UEngineReplicationBridge* const ReplicationBridge = UE::Net::FReplicationSystemUtil::GetActorReplicationBridge(this);

  const UE::Net::FNetRefHandle RegionPointRefHandle = ReplicationBridge->GetReplicatedRefHandle(RegionPoint);
  
  ReplicationSystem->AddToGroup(RegionPointGroupHandle, RegionPointRefHandle);
}
#

Thing is

#

You can get the ReplicationSystem and the ReplicationBridge anywhere

#

As long as you have an Actor Pointer.

#

You can do this in BeginPlay of the RegionPoint too.

#

If you want to do this "anywhere", you need to just give the Group a unique name. Then you don't need the Handle.

#

E.g.

#
void ARegionPoint::BeginPlay()
{
  Super::BeginPlay();

  UReplicationSystem* const ReplicationSystem = UE::Net::FReplicationSystemUtil::GetReplicationSystem(this);
  const UEngineReplicationBridge* const ReplicationBridge = UE::Net::FReplicationSystemUtil::GetActorReplicationBridge(this);

  const UE::Net::FNetRefHandle RegionPointRefHandle = ReplicationBridge->GetReplicatedRefHandle(this);

  // Find it by name. No need to get the GameMode.
  const UE::Net::FNetObjectGroupHandle RegionPointGroupHandle = ReplicationSystem->FindGroup(TEXT("RegionPointGroup"));
  
  ReplicationSystem->AddToGroup(RegionPointGroupHandle, RegionPointRefHandle);
}
#

And later, when your RPC comes in that a given Client is done with spawning them, you can do this:

#
  UReplicationSystem* const ReplicationSystem = UE::Net::FReplicationSystemUtil::GetReplicationSystem(this);

  // Find it by name. No need to get the GameMode.
  const UE::Net::FNetObjectGroupHandle RegionPointGroupHandle = ReplicationSystem->FindGroup(TEXT("RegionPointGroup"));

  // This is copied from a APlayerController, so you need to get the NetConnection for the ConnectionHandle somehow, but I'm sure you will find a way to get the PlayerController of whoever sent that RPC.
  const uint32 ConnectionId = GetNetConnection()->GetConnectionHandle().GetParentConnectionId();
  ReplicationSystem->SetGroupFilterStatus(RegionPointGroupHandle, ConnectionId, UE::Net::ENetFilterStatus::Allow);
#

That will allow the given PlayerController (NetConnection) for all the RegionPoint Actors.

sterile pawn
#

right

#

ill give it a shot, i was looking at groups but the documentation is really limited

hot mist
#

Yeah, it does give you most of the code I just shared with you fwiw

sterile pawn
#

i know

#

but it lacks any context

#

its just heres code

hot mist
#

True. It really tripped me over with the Exclusion Group.

sterile pawn
#

urgh i crashed cause i think i did it too early

#

the netdriver wasnt created

#

lol

hot mist
#

Originally, I set it up to be a Group per Team with all team members in that group and all owning players being allowed on that group.

But that's not what "Exclusion" means. The Group basically shouldn't hold you and your TeamMates, it should hold what you and your TeamMates aren't allowed to see.

hot mist
#

But this should get you quite far. I'm gonna lie down for a bit. Pretty tired atm.

sterile pawn
#

yeah that would be "Inclusion"

#

i assume but then its a filter

hot mist
sterile pawn
#

soo... 🤷

hot mist
#

My test setup to learn Iris was this: Two teams, with each two players who each have a Pawn.
With the wrong idea of the groups, I made a Group per Team and added the matching Pawns to it and allowed the team members.
The goal was to only see your own team member's pawn. First attempt, that was working but "wrong":

TeamA: Player1, Player2
TeamB: Player3, Player4

GroupTeamA:
Objects: Pawn1, Pawn2
Allowed: Player1, Player2
Disallowed: Player3, Player4

GroupTeamB:
Objects: Pawn3, Pawn4
Allowed: Player3, Player4
Disallowed: Player1, Player2

How it should actually be used is to place the Pawns into the Group that the given Team shouldn't see (instead of their own) and to only allow the other team's members on that group, while disallowing the team's own players.
Cause with that the groups are EXCLUDING Actors for the given team.

TeamA: Player1, Player2
TeamB: Player3, Player4

GroupTeamA:
Objects: Pawn3, Pawn4
Allowed: Player3, Player4
Disallowed: Player1, Player2

GroupTeamB:
Objects: Pawn1, Pawn2
Allowed: Player1, Player2
Disallowed: Player3, Player4

So, kinda looks the same, and works the same, but also only for 2 teams. It will look a lot different with more Actors etc.