How do I write an IJobEntity that can enable and disable IEnableableComponents? I know you can use an ECB, EntityManager, or ComponentLookup, but I’m wondering if there’s a straightforward way like there is with idiomatic foreach. In foreach you can use EnabledRefRW<> to be able to directly set the enabled state. But from my understanding, idiomatic foreach is single threaded. Is there anything like that for IJobEntity?
EnabledRefRW works in jobs as well.
public partial struct WeaponResetTriggerJob : IJobEntity
{
public void Execute(EnabledRefRW<WeaponTrigger> trigger)
{
trigger.ValueRW = false;
}
}
You can add WithOptions attribute ```[WithOptions(EntityQueryOptions.IgnoreComponentEnabledState)]
Oh that’s great know thanks!
Well it looks like I can use EnabledRefRW<> in an IJobEntity’s Execute method, but I can’t also use the component by itself at the same time. I would want to do so so I could access its “value” field. The following gives me compiler errors.
using Unity.Burst;
using Unity.Entities;
[BurstCompile]
public partial struct MySystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
new Job().ScheduleParallel();
}
[BurstCompile]
partial struct Job : IJobEntity
{
[BurstCompile]
public void Execute(
ref MyComponent myComponent,
EnabledRefRW<MyComponent> enabledMyComponent)
{
}
}
}
public struct MyComponent : IComponentData, IEnableableComponent
{
public float value;
}
Here’s the 2 compiler errors I get:
-
Packages\PhysicsCharacterController\Runtime\Systems\TestSystem.cs(20,39): error SGJE0017: global::MySystem.Job has duplicate components of same type MyComponent. Remove all but one to fix.
-
Packages\PhysicsCharacterController\Runtime\Systems\TestSystem.cs(37,37): error CS0426: The type name ‘InternalCompilerQueryAndHandleData’ does not exist in the type ‘MySystem.Job’
I am doing that within a foreach and it works without problems (Entities 1.0.11):
foreach (var (damageFlag, tickDamage, e) in
SystemAPI.Query<EnabledRefRW<TickDamage>, RefRW<TickDamage>>()
.WithOptions(EntityQueryOptions.IgnoreComponentEnabledState)
.WithEntityAccess())
{
tickDamage.ValueRW.Cooldown -= SystemAPI.Time.DeltaTime;
damageFlag.ValueRW = tickDamage.ValueRW.Cooldown <= 0;
if (damageFlag.ValueRW)
tickDamage.ValueRW.Cooldown += tickDamage.ValueRW.Interval;
}
Weird if it works in foreach but not in IJobEntity. Maybe a bug? Maybe IgnoreComponentEnabledState is required in this case?
Yes, you have to specifiy the WithOptionsAttribute using the IgnoreComponentEnabledState for the job:
Really? Even with the attribute, I’m still prevented from querying both the component data ref and its enabled ref in an IJE (1.0.11), and have to use a lookup instead.
public partial struct ModifyDataAndEnableable : ISystem {
[BurstCompile] public void OnUpdate( ref SystemState state ) {
new ModifyDataAndEnableableJob().ScheduleParallel();
}
[WithOptions( EntityQueryOptions.IgnoreComponentEnabledState )]
[BurstCompile] partial struct ModifyDataAndEnableableJob : IJobEntity {
void Execute( EnabledRefRW<TestEnableableData> testEnableableState, ref TestEnableableData testEnableableData ) {
}
}
}
public struct TestEnableableData : IComponentData, IEnableableComponent {
public float someData;
}
Oh, sorry some misunderstanding here. You are right, you have to use a lookup. I also experienced this restriction. The attribute WithOptions(EntityQueryOptions.IgnoreComponentEnabledState) only helps to query disabled EnableableComponents on a job.
@cort_of_unity I’ve seen you answering questions about the Entities package. I hope you don’t mind me pinging you here. Could you comment on this? Is there a way to both read/write a components enabled flag and that same components fields by having it as a parameter in the Execute method of an IJobEntity?
What you can do as a workaround is create an aspect with EnabledRefRW and RefRW of the same component type.
If you use this be aware that you won’t be able to use the WithDisabledAttribute on an IJobEntity (codegen bug?) so as a workaround for this just use an EntityQuery when scheduling the job
Is this still the way to go if one wants to modify and potentially disable a component in the same job?
From 1.1.0-pre.3 onwards you can use [WithPresent(typeof(Enableable))] and/or SystemAPI.QueryBuilder().WithPresentRW<Enableable>().
public struct AbstractState : IComponentData, IEnableableComponent
{
public float checkSeconds;
public float increment;
}
[BurstCompile]
public partial struct AbstractStateUpdateSystem : ISystem
{
private EntityQuery _query;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_query = SystemAPI.QueryBuilder()
.WithPresentRW<AbstractState>()
.Build();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var job = new UpdateJob {
deltaTime = SystemAPI.Time.DeltaTime,
};
state.Dependency = job.ScheduleParallel(_query, state.Dependency);
}
[BurstCompile]
private partial struct UpdateJob : IJobEntity
{
public float deltaTime;
private void Execute(
EnabledRefRW<AbstractState> enabledRef
, ref AbstractState state
)
{
state.increment += deltaTime;
enabledRef.ValueRW = state.increment >= state.checkSeconds;
}
}
}
Is there a way to do this without using EntityQuery/QueryBuilder?
I tried this putting this above the IJobEntity:
[WithPresent(typeof(MaterialMeshInfo))]
[WithPresent(typeof(EnabledRefRW<MaterialMeshInfo>))]
Unfortunately, it still complains about a duplicate component type. It also complains about the second line saying ComponentType must be known at compile time. As far as I can tell, there is no [WithPresentEnabledRefRW()] attribute.
In the changelog 1.1.0-exp.1 https://github.com/needle-mirror/com.unity.entities/releases the Fixed section contains this item: “Users can now specify duplicate components in the same IJobEntity.Execute() method, insofar as exactly one of them is wrapped in EnabledRef.”… this seems to imply that what we’re trying to do should work.
I posted a fix for this on discord and dicussed it with Dani after https://discord.com/channels/489222168727519232/1251658206044754036
I also fixed the [WithChangeFilter(T)] and [WithPresent(T) combination
Both fixes available in my fork here: https://github.com/tertle/com.unity.entities
Specific commit
https://github.com/tertle/com.unity.entities/commit/7c2ea0b6e077a8f71474015a02712a63d8215d57
Basically this will make this work
[WithPresent(typeof(MaterialMeshInfo))]
private partial struct TestJob : IJobEntity
{
private void Execute(EnabledRefRW<MaterialMeshInfo> mmi)
{
}
}
It was already fixed with [WithDisabled] but [WithPresent] was overlooked, so I just applied the same fix.
Haha that’s so cool! With that fix, I think I would be able to do this:
[WithPresent(typeof(MaterialMeshInfo))]
private partial struct TestJob : IJobEntity
{
private void Execute(EnabledRefRW<MaterialMeshInfo> mmiEnabledRef, ref MaterialMeshInfo mmi)
{
}
}
Most devs like myself discover a bug in a framework and then report it… but no, not tertle… he cracks open the framework source code and fixes it! Hopefully Dani or someone can incorporate the fix officially soon since I think it’s a fairly common situation to want to enable/disable a component and write to it in the same IJobEntity. Thanks for the help!
@joshrs926 You should change this to a bug, so it will get the developers attention.
I know this is an old thread, but I got here from Google and decided to put my two cents in.
If you’re checking the state of the component and manipulating it’s values. You’re better off using a ComponentLookup and an ECB. Changing it’s values is going to use the same internal functions as an ECB to write the new changes anyway. Most of the IJobEntity conventions are replaced with the their long-form counterparts by the compiler. There’s no harm in doing it manually, in fact, it’s probably faster.
[WithPresent(typeof(MaterialMeshInfo)]
partial struct TestJob : IJobEntity
{
public ComponentLookup<MaterialMeshInfo> mInfo;
public EntityCommandBuffer.ParallelWriter ECB;
public void Execute([ChunkIndexInQuery] int sortKey, in Entity entity)
{
if (minfo.IsComponentEnabled(entity) &&
minfo.TryGetComponent(entity, out MaterialMeshInfo mmi) {
/* do stuff with mmi */
ECB.SetComponent(sortKey, entity, /*modified*/ mmi);
}
}
}
//then when you use it:
new TestJob() {
mInfo = SystemAPI.GetComponentLookup<MaterialMeshInfo>(),
// this is the ECB boundary at the end of the current frame
// you can use whatever boundary feels appropriate and doesn't cause conflicts
ECB = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>()
.CreateCommandBuffer(state.WorldUnmanaged)
.AsParallelWriter()
};
using an existing boundary means you don’t have to complete and replay the ECB manually.
You can also use “EntityQuery” instances to provide the WithPresent<>() clause. I personally construct my queries in the system’s OnCreate() method using SystemAPI.QueryBuilder() and supply the WithPresent<>() clause there instead of as an attribute on the job itself, this allows the system to control the query, not the job. When performance is a concern, I find the long form to be better since you’re not relying on compiler magic to do it for you. You can also create a new system and implement your own boundary (complete / replay), but I’ve found that doesn’t always play nice with dependency chains, so I use a built-in boundary whenever I need to use an ECB. I’ve been using this strategy since Unity 2022 and I have not had to change or modify it due to API changes.
Completely incorrect. IJobEntity does not use an internal ECB in any way, and instead iterates the relevant data and performs writes directly. While it is possible to recreate this behavior using the chunk iteration APIs, what you propose is in no way faster. Actually, it is quite a bit slower.
test data > opinion
Jobs do not use an internal ECB, when you modify a component passed by ref as a parameter to a job then you are modifying its original location in memory directly. You are not going through any additional buffer, it has already given you access to the component. This is just how ref works in C# by default, its not specific to entities.
Your proposed logic does similar things to the normal route, it still needs to check for the presence of the component, however you are also forcing the job to spend time running for entities that have MaterialMeshInfo disabled, as [WithPresent] includes inactive components. If you instead go through the normal route, it still has to check for it being enabled but it wont make the job waste time by running the Execute method on that entity. You then check if the component is present and active (IsComponentEnabled) and also check if its present again (TryGetComponent), only then do you actually begin to modify it. Once that is done you are scheduling it for playback when the ECB runs, but you didnt modify it at the current point in time. Everything you are doing is what already happens, except you added more redundant logic on top of it and delayed when it actually applies changes.
You say “it’s probably faster” which implies you havent tested, but its quite obvious from looking at it that it quite literally cannot be faster. Its the same logic with more work.