Mutually exclusive components

Say there are components GetAimingFromFoe, GetAimingFromMarker, and GetAimingFromTarget. An entity can only have 1 of those 3 at a time. Is there a succinct way of enforcing that adding one component to an entity will remove all other components from that group?

I know I can achieve this by having each job that adds GetAimingFromX also remove all other GetAimingFromY from an entity first, but that
A. Requires a TON of ecb.RemoveComponent per job
B. Violates the Open-Closed principle

1 Like

If you can guarantee only one component will ever be added per frame, you can use tag components and clever EntityQuery shenanigans to detect the additional component and figure out what to remove. Otherwise, no. Use a static method that takes an ECB and desired component and does the extra work.

Could you elaborate on what I have to do with EntityQuery?

No, but it would be cool to have real ADTs/tagged unions.

I think he is talking about a reactive system.

see https://discussions.unity.com/t/797563/6 for a quick exmplaination
see https://discussions.unity.com/t/797563/30 for a generic implmenetation

feel free to use/modify any of the code :wink:

In your case you just need to detect the addition of a coponent and declare the generic reactive system to remove other exclusive components.

1 Like

That’s the idea, although you don’t actually need SystemState components to do this.
Imagine components A, B, and C are mutually exclusive, and you have also defined TrackingATag, TrackingBTag, and TrackingCTag components to help with the process. All of these components are IComponentData.
Imagine you have component A added in the first frame. Your reactive system sees that an A exists without a TrackingATag component. A also exists without a B or C component. Therefore, A must have just been added and a TrackingATag component also gets added.
Several frames later, B gets added to the entity. Your reactive system sees that B exists without TrackingBTag, but also A and TrackingATag exist. Therefore it knows that B must have been the most recently added component, so it removes A and TrackingATag and adds a TrackingBTag.

That sounds very useful, and I’ll definitely use this pattern in the future. It’s just that I don’t like how adding a new component C to the mix requires that the reactive system also incorporates it. It’s violating the Open/Closed principle, which while may seem anal can result in bloated code really quickly.

You can get back Open/Closed with some power tools. I don’t know if this will work with Tiny, as it uses a little bit of System.Type logic on startup.

First, create an interface for the mutually-exclusive components. Next, create a tracking tag component for each that implements a generic interface using the mutually exclusive component as the generic type. You’ll have to define these two components together, or use code gen or reflection hacks to auto-generate them.

Now you got two kinds of components implementing two interfaces, one for the mutually-exclusive components themselves, and one for the trackers. In your system’s OnCreate, you can find all of these components and pair the mutually-exclusive components with their trackers. Then convert these Components into ComponentType instances which you can store in a pair of NativeArrays or something. You also want two managed arrays of EntityQuery instances, which you will populate with query combinations that align with you ComponentType data structure. You have two EntityQuery styles. The first includes both the mutually-exclusive type and the tracker in All, and includes all the other mutually-exclusive types in Any. In OnUpdate, for each of these queries, remove the “All” components. The second is just the mutually-exclusive type and excludes its tracker. For each of these queries, add the Tracker in OnUpdate. Do this iteration of EntityQueries second.

Now, by simply defining the component and its tracker, the system will automatically integrate it without another line of code.

1 Like

You’ll need one reactive system per execlusive component, you can modify a bit the code I referenced to add a singleton class that contains a native list of component type. Whenever you derive the abstract reactive system, it add the exclusive component type to the native list and pass it as a native array the the jobs.

Now you can add as many exclusive component type as you want without having to modify the base class.
To work around the limitation of having a singleton, you can delegate the singleton reference retreival to the derived class so that you can make multiple exclusive component set.

Edit : of course each exclusive component system has to remove the exclusive component it manages from the list not to remove himself.

1 Like