I’m a bit confused how to change IComponentData values from within JobComponentSystems when we’re dealing with values not references. What I need help with is the appropriate pattern that plays well with Pure ECS.
In my game I have two systems, and the entities in the system that runs first alters the values of the entities in the other system, which runs second.
Some super-simple example code to show the (incorrect) way I’m currently thinking through the problem. Here’s two IComponentData structs:
public struct Inputter : IComponentData {
public ValueHolder Holder;
}
public struct ValueHolder : IComponentData {
public float Value;
}
Each Inputter entity points to a ValueHolder entity, with the aim of changing the value held.
And here’s my two systems. InputterSystem just gets all the Inputter entities to pass the value 1 to all the ValueHolder entities. The ValueHolder System then just prints out what its holding.
public class InputterSystem : JobComponentSystem {
private struct InputterJob : IJobProcessComponentData<Inputter> {
public void Execute(ref Inputter i) {
// Set the value:
i.Holder.Value = 1;
UnityEngine.Debug.Log("Input Value = " + i.Holder.Value);
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps) {
InputterJob inputJob = new InputterJob();
return inputJob.Schedule(this, 64, inputDeps);
}
}
[UpdateAfter(typeof(InputterSystem))]
public class ValueHolderSystem : JobComponentSystem {
private struct ValueHolderJob : IJobProcessComponentData<ValueHolder> {
public void Execute(ref ValueHolder vh) {
// Print out held value:
UnityEngine.Debug.Log("Held Value = " + vh.Value);
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps) {
ValueHolderJob valueHolderJob = new ValueHolderJob();
return valueHolderJob.Schedule(this, 64, inputDeps);
}
}
But of course, the Inputter ends up with a new ValueHolder value and the actual underlying ValueHolder Entity remains with a held value of 0. I’m trying to code by Reference, not by Value.
What’s the pattern I should be using such that ValueHolderSystem encounters ValueHolder entities that have actually been altered by each Inputter entity?
Careful of saying this. It seems you’re missing two important concepts here. Firstly the Inputter
is a component living on an entity. It’s not the entity itself. If the Inputter
component needs to refer to the entity it would be better to directly store the value of the Entity
(my advice would be to use the Entity
type for this). Going this way you can use the Entity
and the The other thing here is that it seems the Inputter
and ValueHolder
components have a 1-to-1 relationship to each other. This would leave me to rather attach both components to the same entity and just set the ValueHolder
component to the correct value. If this isn’t ideal for you and you want to explicitly have a sync step between the input system and the value hold system I would rather change the Inputter
component to have a newValue
float, then in the value holder system update the ValueHolder
component to this new value.
Secondly understand the difference between how struct types and reference types are stored in memory. Struct types are essentially value types which for simplicity sake is copied by value every time it’s required on the stack. This in essence means that the value that lives within your Inputter
component isn’t a reference, but a copy of the initial value. When updating this you’re not updating the original value and it won’t really be possible to do it this way since it’ll be going against the performant design Unity’s been working on for this framework.
I haven’t looked at jobs so I can’t speak to that side of it.
From your description it seems you want to reference ValueHolder entities from Inputter entities however in your code, ValueHolder is not a separate Entity, but just a property of the Inputter component.
So I’m not sure if you want separate entities or are just having problems setting the value of a component.
In case it’s the first, then the ValueHolder needs to be in its own Entity and the Inputter component needs to store a reference to that Entity. So your components would look like this.
public struct Inputter : IComponentData {
public Entity valueHolderEntity;
}
public struct ValueHolder : IComponentData {
public float Value;
}
So Inputter entities would have the Inputter component and ValueHolder entities would have the ValueHolder component. Then somewhere in your code you need to link ValueHolder entities to Inputter entities.
entityManager.SetComponentData<Inputter>(inputterEntity, new Inputter
{
valueHolderEntity = myValueHolderEntity
});
Then in the first system, you can iterate all Inputter entities, get the ValueHolder entity from it and set the ValueHolder.Value property. Like so.
System 1
struct Group{
int Length;
ComponentDataArray<Inputter> inputter;
}
[Inject] Group inputterGroup;
[Inkect] ComponentDataFromEntity<ValueHolder> valueHolderLookup; // Looks up component in another Entity
for(int i=0; i<inputterGroup.Length; i++){
Inputter inputter = inputterGroup.inputter[i];
ValueHolder valueHolder = valueHolderLookup[inputter.valueHolderEntity]; // Retrieve ValueHolder component from other Entity
valueHolder.Value = 1; // Set value
valueHolderLookup[inputter.valueHolderEntity] = valueHolder; // Store component back into Entity
}
System 2
struct Group{
int Length;
ComponentDataArray<ValueHolder> valueHolder;
}
[Inject] Group valueHolderGroup;
for(int i=0; i<valueHolderGroup.Length; i++){
ValueHolder valueHolder = valueHolderGroup[i];
Debug.Log("Held value = " + valueHolder.Value);
}
I haven’t checked any of this code and it’s for normal Systems. Not sure how you do the same with jobs but it shows the principle anyway.
Cyberwiz15, jooleanlogic, thanks for your replies, very helpful.
You’re quite right!
Yeah, this would be the most obvious pattern to adopt. However, in my real game’s system, the equivalent of ‘ValueHolder’ is not merely a value that is set by one ‘Inputter’ but rather holds a value that many ‘Inputter’ components increment by different amounts. So it’s not really a 1-to-1 relationship.
Jooleanlogic - thanks for your code snippets, it seems like your introduction to ComponentDataFromEntity
was specifically what I was looking for. As you’ve suggested, I’m passing round references to the Entities themselves and drawing them out of an injected lookup and writing them back in when required.
Very helpful! Much appreciated.
One further question, for anyone who has experimented with jobs. This is my InputterSystem right now:
public class InputterSystem : ComponentSystem {
struct Group {
public int Length;
public ComponentDataArray<Inputter> inputter;
}
[Inject] Group inputterGroup;
[Inject] ComponentDataFromEntity<ValueHolder> valueHolderLookup; // Looks up component in another Entity
protected override void OnUpdate() {
for (int i = 0; i < inputterGroup.Length; i++) {
Inputter inputter = inputterGroup.inputter[i];
ValueHolder valueHolder = valueHolderLookup[inputter.valueHolderEntity]; // Retrieve ValueHolder component from other Entity
valueHolder.Value += 1; // Add the value
valueHolderLookup[inputter.valueHolderEntity] = valueHolder; // Store component back into Entity
}
}
}
How might I rewrite the OnUpdate linear for-loop into a Job that spreads all the members of inputterGroup parallel over worker threads? The JobComponentSystem I was extending previously seems to just do it automagically, but is a parallel job still available to me if I’m injecting multiple Component datas?
@Matt-Cranktrain You don’t need reference in Inputter. You can leave it empty and make system that needs Inputter and ValueHolder and then change value in the second one.
Edit. I don’t think there’s possibility to use references in jobs anyway.