Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Entity Command Buffer System and LocalToWorld update issues

Discussion in 'Entity Component System' started by Abbrew, Apr 18, 2020.

  1. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I am having issues getting my ECBSs to synchronize properly. The problem is that upon instantiating an Entity and setting its Translation in a System running in the Simulation group, the TransformSystemGroup does not write the Translation to LocalToWorld in time for another System in the same Simulation group to read the updated position. As a result when the second System reads the LocalToWorld from the instantiated Entity, the value is zeroed out. I've tested delaying the reading by one frame and it works, so the question is how to instruct the System or ECBS to update in a way that doesn't involve jumping through hoops to delay a System by one frame. I've tried the suggestion by @LazyGameDevZA
    but this only solved the issue of LocalToWorld not updating before rendering in the Presentation group
     
  2. gnostici

    gnostici

    Joined:
    Jul 27, 2013
    Posts:
    23
    If you want ThisSystem to update before ThatSystem, and they're in the same update group


    Code (CSharp):
    1. [UpdateBefore(ThatSystem)]
    2. public struct ThisSystem : SystemBase
    3. { ... }
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    If you're instantiating an entity through code, you really should be setting LocalToWorld
     
    NotaNaN likes this.
  4. gnostici

    gnostici

    Joined:
    Jul 27, 2013
    Posts:
    23
    How do you do that from within a parallel job? LocalToWorld captured in ForEach allows access to nothing at all, so you can't change .position directly. And this is what Transform is for. If you set LocalToWorld, and overwrite it, then can't that get overwritten again by RenderMesh? And this also means more has to be cached outside a job. Which kind of defeats the purpose of the parallel job, since you then have to loop over all entities in your query.
     
    Last edited: Apr 18, 2020
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    I don't really get the problem you seem to think exists.

    Entity conversion sets local to world and you should replicate this behaviour if you're doing it by hand.
     
  6. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I don't think this would help. The systems are using entity command buffers so in the end the changes will be applied at around the same time at an ECBS.

    I've tried this but LocalToWorld is still zeroed out somehow. On second thought, it might be because TRSToLocalToWorldSystem is copying the zeroed out Translation to LocalToWorld. Removing the Translation component should solve it, but it feels hacky and non-idiomatic. Do you know any alternatives?
     
  7. gnostici

    gnostici

    Joined:
    Jul 27, 2013
    Posts:
    23
    The problem is that LocalToWorld isn't updating, which implies there is a LocalToWorld component.

    Above your class declaration...

    Code (CSharp):
    1. [UpdateInGroup(typeof(SimulationSystemGroup))]
    2. [UpdateBefore(typeof(TransformSystemGroup))]
    In class scope...

    Code (CSharp):
    1. {
    2.     BeginSimulationEntityCommandBufferSystem m_EndSimECBSys;
    In OnStartRunning...


    Code (CSharp):
    1.         m_EndSimECBSys = World
    2.             .GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
    In OnUpdate() (or wherever you schedule your job)...

    Code (CSharp):
    1. var comBuffer = m_BeginSimECBSys.CreateCommandBuffer().ToConcurrent();
    That schedules your ECB for the beginning of the simulation group update, so it should play back before the transformation group updates. If you've done this, and it doesn't, then the problem comes from elsewhere. In that case, I'm not sure there's much help without looking at code.
     
    Abbrew likes this.
  8. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    @gnostici The issue OP is referring to is that the LTW component is only updated during the TransformSystemGroup, when LTW is written, see https://docs.unity3d.com/Packages/com.unity.entities@0.9/manual/transform_system.html

    So, @Abbrew, if you want LTW written earlier than the end of TransformSystemGroup, you have to write LTW yourself, as @tertle suggests (and have a sync point before your system, as @gnostici suggests). If the system that needs an updated LTW can wait until after TransformSystemGroup, then you can write to Translation, Rotation, etc. and the systems in TransformSystemGroup will write LTW for you.

    I hope that helps :)
     
  9. gnostici

    gnostici

    Joined:
    Jul 27, 2013
    Posts:
    23
    You guys correct me without reading what I post. I'm only trying to help :(

    But yeah, that's why I advised that it should be scheduled. I'm wrestling with a similar timing issue myself (planning design choices), so I've looked over that page a few times in the last day. Just convenient timing to see this issue.
     
  10. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Thank you all for your suggestions. I've come across a solution where I write to both Translation and LocalToWorld of an instantiated entity. LocalToWorld so that the other system will read the correct value on the next frame, and Translation so that the other system will read the correct value after the next frame
     
    florianhanke likes this.
  11. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    yes that is exactly what you need to do.
     
  12. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    Yeah so @tertle is making a very valid point. When creating entities you need to be very deliberate with how certain values are set. One way Unity could go about making this easier is by providing utility functions that can return all the components with their correct values set which can then just be added using the ECB.

    @Abbrew The important part I omitted from my example was other systems wanting access to the LTW component after instantiation. The quick fix would be to make sure that the relevant system updates AFTER the TransformSystemGroup. This can be done by adding the below code to the system that's interested in the LTW values.

    Code (CSharp):
    1. [UpdateInGroup(typeof(SimulationSystemGroup))]
    2. [UpdateAfter(typeof(TransformSystemGroup))]
    Now what's important to understand is why this is the case. The current transform system works off of the back of some more "lightweight" components that make it somewhat easier for us humans to reason bout an enity's spatial data. These components are the Translation, Rotation and Scale components respectively (there are various Rotation and Scale variations though, but that's beside the point). The Transform systems use these components to build up a LocalToWorld and LocalToParent matrix which makes the rendering side's programming much easier, because in rendering code working with matrices is simpler than with vectors.

    Now the gotcha here lies in how we've previously been used to Unity "hiding" the 4x4 matrix from us and just encapsulating everything into a Transform. This had hidden how much was going on behind the scenes, but instantiation would very likely go and setup the necessary LocalToWorld and LocalToParent values when a GameObject is created and we'd miss this happening. With Entities the creation of entities isn't tied to something that'll always be spatial and the programmer is left to figure out what is created and what isn't.

    Tertle's suggestion to manually setup the LTW and Translation values is the main point here. It ensures that the entity is considered ready to use once it's been created and it doesn't have to go through further initialisation steps to have it ready by the time the simulation systems start running. There is a lazy way around this though, which is to make sure your Translation, Rotation and Scale values are set (while also having added a LocalToWorld component), but to then make sure the relevant systems interested in the LTW value should only run after the Transform systems had propagated the data over to the relevant values. One could even argue that there's a need for the Transform systems to also run in the InitializationSystemGroup, but that's going to add some level of overhead and ultimately the programmer that is a little more comfortable working with LTW directly will be able to write more performant code in the long run.
     
    NotaNaN and Abbrew like this.
  13. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Thank you for this writeup! It was very informative.

    I'll go ahead and test this out, but beforehand I'd like to argue that this would mean that the system interested in LTW would "know too much" about its dependencies from a design standpoint. In my use case, the system interested in LTW operates on entities that may or may not have been recently instantiated. I do want to ask though, do I still need to call transformSystemFinalJobHandle.Complete()?
     
  14. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    There should be no need to call transformSystemFinalJobHandle.Complete().

    I also would argue the system knowing too much about its dependencies isn't that big of an issue. My main reasoning is that you're building on top of the Transform system's data so there will be inherent dependencies that'll have to be expressed in some way. It's also really difficult to infer what each developer would want in specific cases, which means I don't quite see a better solution to the problem.

    If you really feel it's a problem going the CustomBootstrap direction would be the only alternative. That way you can be very selective over what systems you include in your game-loop and what their ordering should be. It's ultimately a trade-off you'll have to make. I suspect most serious attempts to build something will have a custom bootstrap where the system update order will be very selective over what systems are run at each stage, while the quick-and-dirty attribute ordering will stay behind purely for ease of use in some cases.

    Now admittedly I would most likely stick to a custom bootstrap for most of the stuff I do, but it'll only be something that's done once the idea I've prototyped grows into something bigger.
     
    Abbrew and NotaNaN like this.