Search Unity

Question Selecting a job to schedule from a map at runtime

Discussion in 'Entity Component System' started by Stoh, Oct 14, 2022.

  1. Stoh

    Stoh

    Joined:
    Dec 20, 2016
    Posts:
    5
    I am using Entities 0.51 and I'm trying to convert existing code (that probably won't work anymore in 1.0). I'd like to launch various jobs depending on an enum value at runtime.

    I have abilities that can both gather potential targets (using a IJobEntity.ScheduleParallel() would be great) and be used (a IJob.Run() would work here since only one ability can be used at a time). There are lots of different abilities, so the less boilerplate code, the better.

    Basically, I'd like to use an interface and put job factories (one per ability type) in a map. Then systems can use this map to create and launch various jobs depending on the ability type.

    Code (CSharp):
    1. public interface IAbility {
    2.     AbilityEnum Key {get;}
    3.     IJob CreateUseAbilityJob(EntityManager entityManager);
    4.     IJobEntity CreateGatherTargetsJob(EntityManager entityManager);
    5. }
    But I'm not sure it's possible at all. So far I didn't find a way to do that:
    - IJobEntity / IJob typed vars can't be launched directly (they're not struct)
    - they can't be scheduled from a generic method AFAIK (code generation doesn't seem to handle that)
    - they can't be scheduled from a static method.

    Is there a way that wouldn't involve a giant switch to create the correct job (to reduce boilerplate code)?
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Couple of options:
    - Use generic approach to the data. Avoid interfaces overall and go for composition instead of inheritance.
    For example, gather data required based on some T attached to the entity in one system. Process ability logic X in different one based on T, or T1 or whatever is attached to the entity.

    - If you're willing to with "typed" enum approach, I'd recommend using single job instead of scheduling multiple different ones. That will massively reduce LOC required to be written.
     
  3. Stoh

    Stoh

    Joined:
    Dec 20, 2016
    Posts:
    5
    Thank you for your answers!

    I think the first option as I see it might be a bit too much LOC indeed, wouldn't that mean writing something like a system by ability type (or a very big system with a lot of queries)?

    I can't really go with with one single job either AFAIK, as the target gathering queries may use different components depending on the ability.

    That makes me think that in the end, a giant switch that launch the correct job(s) depending on the ability type might be the more concise and efficient option after all.
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Ideally what you want is to process data based on which data is present.
    That way each ability would have its own system / related queries instead of large switch state with jobs. No need to use interface for those as well.

    Also,
    Gather everything you need beforehand, and use it (optionally) afterwards.
    Less branching this way, akin to shaders.

    For example, if Entity has DynamicBuffer<NearbyDoSomethingX> it should do it before processing ability.
    But it does not care which ability is added at the data gathering step.

    If entity does not have some attached data, according ability won't be processed, since queries won't be triggered. So when something has to be triggered either add tag component, or enable it (depending on Entities version been used).

    If that's an ability that is being operated on, and its one per map, you could add an Entity with required data for an ability. Then, separate systems can process those abilities, and you won't need a switch, since logic will be split into neat systems based on queries. Gathering / Prepare step can be executed by schedulling IJobEntity-ies and code can be re-used that way. Plus you won't lose on system ordering this way.

    Enums (types) are useful in cases where behavior defer, but general data processing may be the same for all (e.g. data gathering). This reduces LOC.

    TL;DR:
    -> Author entity for an ability (upon load map load or otherwise);
    [ Game Loop:
    [ -> Action Required (query triggered by added tag / data / enabled component)
    [ -> Prepare Data (Gather what you need, in buffers or as a state);
    [ -> Perform something (based on data);
    [ -> Reset (action tag or enabled state) if its required;
     
    Last edited: Oct 18, 2022
  5. Stoh

    Stoh

    Joined:
    Dec 20, 2016
    Posts:
    5
    I understand, but if there's one system by ability type, that would make 50+ systems for one feature (abilities). Wouldn't that be a bit too much?

    For available targets gathering that may be the way to go indeed. The new aspects of 1.0 may be used for that too if I understood well.

    I think behavior is mostly unique for each ability type. While the data they need might sometime be close, there's not a lot of common behavior when an ability is actually used. Like one ability type can teleport a pawn, another will apply a buff (which rely on entirely different data) to pawns in an area, one might do something based on specific criterion, another might impact AI, etc.

    However concerning the ability execution part it's definitively ok to do it on main thread. It sometimes need to query entities (again in very specific ways) but I guess that can be done outside of a job/system with a bit more LOC.
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Depends. If most are generic, and work on the same subset of logic, it would be drastically less.
    If they're unique - logic that is separated is easier to extend or modify. Easier to maintain. Plus (system) order.

    Extra ISystems are really close to be "free" performance wise, if thats the concern.
    As for the job scheduling cost, its unavoidable even if put inside switch statement.
     
  7. Stoh

    Stoh

    Joined:
    Dec 20, 2016
    Posts:
    5
    Thank you for your advice. I don't think using a different component per ability type will really work for my use case. However preparing the data in advance so that the actual target collection can be made fast & simple was really helpful!
     
    xVergilx likes this.