In the old workflow, coroutines were the best way to wait. (They even had better performance than C#'s async await).
The question is, how we should write delayed code in ECS?
Only way I can think of to do it correctly is with code gen or manual translation. Basically every coroutine can be converted into a component and a job in which all local variables of the coroutine are members of the component and then the component also has a jump index so that when the job starts it jumps to after the “yield” point and continues logic to the next “yield”.
I’m suspicious someone at Unity is trying that codegen approach to convert coroutine functions into component-job pairs, otherwise I bet someone from the community will figure it out.
I think best will be just introduce a specific “Delay” component.
Attach it to the entity which needs to be waited. (Equal to WaitForX)
Query entities to the system only if the “Delay” component is not present on the entity.
(In your process system, e.g. attack or whatever)
Tick delay in a different system (simply decrement the delay value, remove component via EntityComponentBuffer)
Process your entity as usual.
Alternatively, you could have a system state component that contains data about the delay.
And process it in the similar way.
That’s a good idea. The downsize is it introduces coupling to existing systems that needs to respect the delay. Now you have to add ComponentType.Exclude() to the queries of such systems.
Any 2021 thoughts on this?
I needed Function_B to be called one frame after Function_A, where each function is only ever called once. I created a system for each and organized them in the following way, enabling and disabling the systems as needed:
System execution order:
Frame N:
- [DISABLED] Function_B
- Function_A
Frame N+1:
- Function_B
- [DISABLED] Function_A
Frame N+2:
- [DISABLED] Function_B
- [DISABLED] Function_A
This felt a bit contrived so I thought I’d post here in case people have come up with better solutions.
You could execute system B before system A.
So when A is executed, it need wait until next frame, to execute B.
You can also use ECB, to control when entities are executed.
Oh, I see. Have System B look for entities with a component like ConsumeMeSystemBTag
, where System A is the only thing that will add this component to an entity. Then System B can remove the component so it’s not called again.
Thanks!
EDIT: I end up destroying these one-time initialization systems after using them, but if I end up needing them after initialization I’ll switch to the more elegant entity-based toggle approach.