Search Unity

Question Add a ISystemStateBufferElementData during conversion?

Discussion in 'Entity Component System' started by PhilSA, Jul 28, 2022.

  1. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Quick version:
    I'm trying to add a ISystemStateBufferElementData to an entity during conversion, using "dstManager.AddComponent<MyStateBuffer>(entity);". But it looks like it's not adding the buffer.

    I know Unity.Transforms.Child is a ISystemStateBufferElementData and it seems to be added during conversion (according to the dots hierarchy window at least), but I haven't been able to find how it gets added so far in the source code. Can't find any "AddBuffer<Child>"

    Does anyone know if it's possible to add those during conversion?


    Long version:
    I'm trying to add a ISystemStateBufferElementData to an entity in an authoring component, like this:
    Code (CSharp):
    1. [DisallowMultipleComponent]
    2. public class MyAuthoring : MonoBehaviour, IConvertGameObjectToEntity
    3. {
    4.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    5.     {
    6.         dstManager.AddBuffer<MyStateBuffer>(entity);
    7.     }
    8. }
    But it looks like this fails to add the buffer.

    The reason why I want this is because:
    1. I need a state buffer that will remember some things for a cleanup when an entity is destroyed
    2. I need it to come built-into the prefab entity. Because I need to be able to instantiate that entity from ECB, and append elements to that buffer immediately, without having any sync points in between. And when I say "immediately", it's because the elements of that buffer will need to be read before any sync point happens as well. So I also want to avoid ecb.AppendToBuffer(), and I want to get the buffer from entity and write directly to it instead.
    Basically, the situation is a bit complex to justify, but I'm trying to design a fool-proof user-friendly system, and relying on ECBs to add that buffer could lead to a whole lot of problems if you're not extra-careful with where your ECBs are processed in the frame or when you call certain functions. I don't want the user to have all kinds of pitfalls to remember. The ideal solution would be to be able to add that state buffer during conversion.

    Does anyone know why it can't be done, or if there are actually ways to make it work?
     
    Last edited: Jul 28, 2022
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    For better or worse you can't. By design system states are not copied between worlds or entity instantiations.
     
    Last edited: Jul 28, 2022
    Anthiese likes this.
  3. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    > if you're not extra-careful with where your ECBs are processed in the frame or when you call certain functions

    Isn't that a good use-case for component system groups? Just add your own ECB system and you can make stages very safe. Even make it clear where and why users update in certain groups.
    Having well designed sync points is better than relying on End/StartSimulationECBs.
     
    Anthiese likes this.
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Let's say I proposed a solution that required you to add a component via ECB. But the solution I propose would still work if systems before the ECB needed to access data.

    There's still one failure case, and that is if a system before the current one has already recorded the entity to be destroyed in the same ECB system you would use to add the component. Which would mean during ECB system playback, the entity would be destroyed first, and then crash when the second ECB tried to add the component.

    Do you care about this use case? Because if not, I can propose a three-step solution to solve this problem.

    If you do care, then you either need to make this system synchronous (what I do for a very similar problem: https://github.com/Dreaming381/Lati...yncPoint/SkeletonMeshBindingReactiveSystem.cs ) or you need to use an observer pattern which polls for entity existence and retains whatever you would have retained in the dynamic buffer.
     
  5. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    I think I'll just have to explain the precise use case. Sorry in advance for the long post!
    1. I have "Effect" entities and "Actor" entities.
    2. An example of an Effect is something that reduces move speed by 5 when applied to an actor
    3. Since Effects always have a single target, I want all Effects applied to an actor to get auto-destroyed when the actor is destroyed (saves us from forgetting to destroy them and having all those orphaned Effect entities polluting the scene)
    4. Some Effects cannot stack, and so they must do some check to see if an effect of that same type is already applied to a target actor
    5. I want to avoid any reliance on sync points and change-filtering jobs as much as possible. It would be too easy to forget that spawning any effect-compatible entity after a certain point in the frame could lead to game-breaking bugs. And as for change-filtering jobs, I need this system to work in the context of netcode prediction, which means that the constant cost of these would be multiplied by 5-10 on average and would become significant (especially since these archetypes will tend to be large, so very little will fit in a chunk)

    So imagine this:
    • You spawn one Actor (ActorA), and two non-stacking Effects of the same type (EffectA, EffectB) that are assigned ActorA as their target. They all spawn exactly at the same time
    • Effects have a job that automatically applies them to their target when they're created, and automatically unapplies then when destroyed (this uses SystemState components, but that's not where the issue is)
    • When a non-stacking effect is applied to a target, it must look at a DynamicBuffer<CurrentlyAppliedEffectEntities> on the target Actor, to see if an effect of the same type already exists. If it does, it skips its applyage and self-destructs
    • But since multiple non-stacking effects of the same type were spawned simultaneously, and the Actor doesn't spawn with its DynamicBuffer<CurrentlyAppliedEffectEntities> already added, if we used ECB.AppendToBuffer to add them to the DynamicBuffer<CurrentlyAppliedEffectEntities>, they would not be able to tell that another effect of the same type is already present in that buffer. You'd get two non-stacking effects applied to the Actor
    • That DynamicBuffer<CurrentlyAppliedEffectEntities> happens to be the problematic ISystemStateBufferElementData I was talking about. It's a state component because when the Actor is destroyed, I want to destroy all effects affecting it as well
    I'm not sure I can use LinkedEntityGroup to handle the "destroy applied effects when destroying an actor" logic, because I think I might have to handle extra logic there as well (beyond just the destruction of the entity). I'm just not sure yet.

    Alternatives would be:
    • tell users to always remember to manually add a DynamicBuffer<CurrentlyAppliedEffectEntities> whenever they instantiate an entity that can receive effects.
    • force users to spawn all actors before a certain sync point that ensures the addition of that buffer
    • force effect-applyage to be delayed by 1 frame, by simply not handling effect-applying logic as long as a DynamicBuffer<CurrentlyAppliedEffectEntities> is not present on the target Actor
    • Make actors come with a regular IBufferElementData for the current effects, but have a change-filtering job transfer all those elements to an actual ISystemStateBufferElementData that gets added later
    Some of these don't necessarily sound terrible, but I still want to think of alternatives that would make this truly 100% fool-proof and without delays
     
    Last edited: Jul 29, 2022
  6. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Wait, I take back what I said

    I can totally just use LinkedEntityGroup to handle effect destruction. If I want custom effect-destroying logic, I can simply have a ISystemStateComponent on the effects themselves to handle that.

    And since LinkedEntityGroup is a regular buffer type, I can ensure it gets added on all effect-compatible entities during conversion.

    Case closed!
     
    Last edited: Jul 29, 2022