Upgrading to Entities 1.4
Entities 1.4 has some changes that might introduce warnings to your project. To fix those warnings, do the following:
- Change Entities.ForEach code
- Change Aspects code
- Change EntityCommandBuffer PlaybackPolicy code
- Migrate PostLoadCommandBuffer to RequestSceneLoaded.ImportEntity
Change Entities.ForEach code
To consolidate the Entities API and improve iteration time, Entities.ForEach is deprecated in Entities 1.4, and you should use either IJobEntity or SystemAPI.Query.
IJobEntity
Because IJobEntity Execute methods support ref and in parameters to denote read-only and read-write status, you can often copy the lambda of an Entities.ForEachinto the Execute method for the IJobEntity job struct. Additionally, IJobEntity supports all the scheduling options that Entities.ForEach supports.
Note
IJobEntity isn't Burst-compiled by default and it can't capture variables because there is no lambda body. Use the [BurstCompile] attribute to enable Burst compilation and write captured variables into fields on the job struct.
Code example using Entities.ForEach
public partial class RotationSpeedSystemForEachISystem : SystemBase
{
protected override void OnUpdate()
{
float deltaTime = SystemAPI.Time.DeltaTime;
Entities
.ForEach((ref LocalTransform transform, in RotationSpeed rotationSpeed) =>
{
transform.Rotation = math.mul(
math.normalize(transform.Rotation),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
})
.ScheduleParallel();
}
}
Code example using IJobEntity
[BurstCompile]
public partial struct ASampleJob : IJobEntity
{
public float DeltaTime;
void Execute(ref LocalTransform transform, in RotationSpeed rotationSpeed)
{
transform.Rotation = math.mul(
math.normalize(transform.Rotation),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime));
}
}
public partial class ASample : SystemBase
{
protected override void OnUpdate()
{
var deltaTime = SystemAPI.Time.DeltaTime;
new ASampleJob{ DeltaTime = deltaTime }.ScheduleParallel();
}
}
For more information about IJobEntity, refer to Iterate over component data with IJobEntity
SystemAPI.Query
For entity iteration that doesn't have to happen in a job (but can still be Burst compiled), SystemAPI.Query can provide a simpler option becauseSystemAPI.Query utilizes RefRO and RefRW types to wrap type parameters that are accessed as read-only and read-write status respectively. There are additional builder methods on Query to indicate WithAll, WithNone, WithAny and other options.
The following changes the previous Entities.ForEach example to use SystemAPI.Query
public partial class ASample : SystemBase
{
protected override void OnUpdate()
{
var deltaTime = SystemAPI.Time.DeltaTime;
foreach (var (transform, rotationSpeed) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>())
{
transform.ValueRW.Rotation = math.mul(
math.normalize(transform.ValueRO.Rotation),
quaternion.AxisAngle(math.up(), rotationSpeed.ValueRO.RadiansPerSecond * deltaTime));
}
}
}
For more information about SystemAPI.Query, refer to Iterate over component data with SystemAPI.Query.
Change Aspects code
Aspects is deprecated from Entities 1.4, and there's no direct replacement for them. Instead you must replace the abstraction with explicit code that queries for the correct set of components and performs the expected operation on them. The following code provides a simple example of converting an aspect and its usage into an explicit EntityQuery and a helper method designed to perform the operation.
Code example using Aspects:
public partial struct RotationSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var deltaTime = SystemAPI.Time.DeltaTime;
var elapsedTime = SystemAPI.Time.ElapsedTime;
foreach (var movement in SystemAPI.Query<VerticalMovementAspect>())
{
movement.Move(elapsedTime);
}
}
}
readonly partial struct VerticalMovementAspect : IAspect
{
readonly RefRW<LocalTransform> m_Transform;
readonly RefRO<RotationSpeed> m_Speed;
public void Move(double elapsedTime)
{
m_Transform.ValueRW.Position.y = (float)math.sin(elapsedTime * m_Speed.ValueRO.RadiansPerSecond);
}
}
Code example using EntityQuery:
public partial struct RotationSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var elapsedTime = SystemAPI.Time.ElapsedTime;
foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>())
{
VerticalMovementHelper.Move(elapsedTime, transform, speed);
}
}
}
static class VerticalMovementHelper
{
public static void Move(double elapsedTime, RefRW<LocalTransform> transform, RefRO<RotationSpeed> speed)
{
transform.ValueRW.Position.y = (float)math.sin(elapsedTime * speed.ValueRO.RadiansPerSecond);
}
}
Change EntityCommandBuffer PlaybackPolicy code
The PlaybackPolicy enum is deprecated in its entirety and will be removed in a future version. This includes both PlaybackPolicy.SinglePlayback and PlaybackPolicy.MultiPlayback, as well as any EntityCommandBuffer constructor overload that takes a PlaybackPolicy parameter.
Going forward, SinglePlayback is the only supported behavior and is the default: an EntityCommandBuffer can be played back only once. Create an EntityCommandBuffer without specifying a PlaybackPolicy. If you need to apply the same set of commands more than once, record them again into a new EntityCommandBuffer for each playback.
Migrate PostLoadCommandBuffer to RequestSceneLoaded.ImportEntity
The PostLoadCommandBuffer managed IComponentData is deprecated and will be removed in a future version. Replace it with RequestSceneLoaded.ImportEntity, which delivers a regular main-world entity (and all of its components) into the section's streaming world before ProcessAfterLoadGroup runs.
To migrate:
- Build the per-instance data on a regular entity in the main world, directly adding the components you previously recorded into the
EntityCommandBuffer. - Pass that entity as
ImportEntityin theSceneSystem.LoadParametersyou give toSceneSystem.LoadSceneAsync(or writeRequestSceneLoaded { ImportEntity = dataEntity }on the scene or section meta entity). - Your existing
ProcessAfterLoadsystem queries the imported components exactly as before; the carrier entity appears in the streaming world. Destroy it inside that system if you don't want it to survive into the main world after the load.
You own the source entity in the main world: keep it alive until the load completes, and destroy it yourself when it's no longer needed.