diff --git a/CHANGELOG.md b/CHANGELOG.md index 2837da32..3d9e16d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ uid: changelog # Changelog +## [1.3.2] - 2024-09-06 + +### Added + +* Overloads for `ComponentLookup.HasComponent`, `ComponentLookup.TryGetComponent`, `BufferLookup.HasBuffer`, and `BufferLookup.TryGetBuffer` adding parameter `out bool entityExists`, as well as dedicated `ComponentLookup.EntityExists` and `BufferLookup.EntityExists` APIs, to allow user-code to distinguish entity non-existence from component non-existence without additional boilerplate, inside jobs. +* adding missing dependencies `com.unity.modules.physics`, `com.unity.modules.uielements`. + +### Changed + +* Updated Burst dependency to version 1.8.17 +* Add API docs discouraging the use of the `ExclusiveEntityTransaction.EntityManager` property. Many EntityManager operations are not safe to use within the context of an ExclusiveEntityTransaction; only the methods directly exposed by `ExclusiveEntityTransaction` are guaranteed to work correctly. +* Add API docs discouraging the creation of `EntityQuery` objects with multiple query descriptions. This feature works in narrow contexts, but is known to cause errors and incompatibilities with other DOTS features. +* Add API docs to note that enabling and disabling components inside `IJobEntityChunkBeginEnd.OnChunkBegin()` does not affect the entities iterated in the current chunk, as its `chunkEnabledMask` has already been computed. +* Zero-size `IBufferElementData` and `ISharedComponentData` structs no longer cause the TypeManager to throw during initialization. Zero-size buffer and shared components are usually a sign of pointless waste (especially buffer components, which have a significant chunk-memory cost even if the actual elements are empty), but they shouldn't cause a fatal error. + +### Fixed + +* Various SGICE002 errors that happen if you type invalid C# code +* Various SGICE003 errors that happen if you type invalid C# code +* NullReferenceException on UnityObjectRef after Asset Garbage Collection (This fix requires editor versions 2022.3.43f1 and 6000.0.16f1 and beyond) + + ## [1.3.0-pre.4] - 2024-07-17 ### Changed @@ -22,6 +44,7 @@ uid: changelog * EntityComponentStore leaked memory during domain reload. + ## [1.3.0-exp.1] - 2024-06-11 ### Added @@ -68,6 +91,18 @@ uid: changelog ### Known Issues +## [1.2.4] - 2024-08-14 + +### Fixed + +* Debug proxies (used by external debuggers) were sometimes using invalid field offsets when inspecting structs in blob assets. This led to incorrect values being reported in debugger watch windows. In particular, this would be triggered by the use of bool fields in blob asset structs. +* Entity version numbers could go back to 1 after reallocation in some edge cases. +* When building a content update, a temporary path was getting created in the drive root instead of the Library folder. This would also cause content update builds to grow in size every time they were built. The folder is now created in the Library correctly. +* Error in build when sprites are contained in subscenes has been removed. +* Regression in compilation time with assemblies with lots of system methods. +* EntityComponentStore leaked memory during domain reload. + + ## [1.2.3] - 2024-05-30 ### Fixed diff --git a/Documentation~/concepts-safety.md b/Documentation~/concepts-safety.md index ecadaab9..660a1071 100644 --- a/Documentation~/concepts-safety.md +++ b/Documentation~/concepts-safety.md @@ -17,6 +17,12 @@ One of the most common issues with safety in Entities is when [structural change The Entities API stores data in chunks that are typically accessed through the [job system](xref:JobSystem) or the main thread. The job system typically handles all safety of data that's passed in with NativeContainers, and uses notations to mark if the data is read from, written to, or both. However, any API that causes a structural change might make this data move in memory and invalidate any reference held to that data. +#### ExclusiveEntityTransaction + +In general, all structural changes must be made on the main thread using the world's `EntityManager`. The `ExclusiveEntityTransaction` feature allows you to temporarily place an `EntityManager` into a mode where a single worker thread (running an `IJob`) can safely perform structural-change operations on that World's entities, leaving the main thread free to perform other work. + +The main motivation for this feature is to allow a secondary / streaming World to safely modify its entities and perform structural changes, without blocking the main thread from processing entities in the default World. It is not intended as a fully general-purpose interface to `EntityManager` functionality from worker threads. Certain `EntityManager` operations rely on main-thread-only features in their implementation, and will therefore not function correctly if called from job code. Only the subset of operations directly exposed by `ExclusiveEntityTransaction` are officially supported. + ### RefRW/RefRO The Entities package contains explicit reference types that you can use to mark the contained type to be accessed as ReadWrite (`RefRW`) or ReadOnly (`RefRO`). These reference types have checks to ensure that the contained type is still valid when running with safety checks enabled. [Structural changes](concepts-structural-changes.md) might cause the contained type to no longer be valid. @@ -34,4 +40,4 @@ There are a few cases that aren't guarded against. This section outlines any cas The `InternalCompilerInterface` static class includes a number of methods that expose some of the DOTS internals to source-generated code. This is necessary because generated code can only typically call public APIs. >[!WARNING] ->Do not use the APIs contained in InternalCompilerInterface. They are only in the context of being called from generated code and are likely to change in the future. \ No newline at end of file +>Do not use the APIs contained in InternalCompilerInterface. They are only in the context of being called from generated code and are likely to change in the future. diff --git a/Documentation~/index.md b/Documentation~/index.md index e4bf7a17..f14bf59b 100644 --- a/Documentation~/index.md +++ b/Documentation~/index.md @@ -10,7 +10,6 @@ See the [DOTS Guide and Samples](https://github.com/Unity-Technologies/EntityCom * 2022.3 (LTS) * 2023.3 (Latest Beta and beyond) -* Unity 6 ## Package installation diff --git a/Documentation~/systems-entityquery-create.md b/Documentation~/systems-entityquery-create.md index f1230681..b3ab771c 100644 --- a/Documentation~/systems-entityquery-create.md +++ b/Documentation~/systems-entityquery-create.md @@ -8,12 +8,14 @@ The query uses [`EntityQueryBuilder.WithAllRW`](xref:Unity.Entities.EntityQue ## Specify which archetypes the system selects -Queries only match archetypes that contain the components you specify. You can specify components with three different [`EntityQueryBuilder`](xref:Unity.Entities.EntityQueryBuilder) methods: - -* `WithAll()`: To match the query, an archetype must contain all the query's required components. -* `WithAny()`: To match the query, an archetype must contain at least one of the query's optional components. -* `WithNone()`: To match the query, an archetype must not contain any of the query's excluded components. -* `WithAspect()`: To match the query, an archetype must meet the [aspect’s](aspects-intro.md) component requirements. Use last when building a query to avoid component aliasing. +Queries only match archetypes that contain the components you specify. You can specify components with the following [`EntityQueryBuilder`](xref:Unity.Entities.EntityQueryBuilder) methods: + +* `WithAll()`: To match the query, an entity's archetype must contain all the query's required components, and these components must be enabled on that entity. +* `WithAny()`: To match the query, an entity's archetype must contain at least one of the query's optional components, and these components must be enabled on that entity. +* `WithNone()`: To match the query, either an entity's archetype must not contain any of the query's excluded components, or the components must be present but disabled on that entity. +* `WithDisabled()`: To match the query, an entity's archetype must contain this component, and the component must be disabled on that entity. +* `WithAbsent()`: To match the query, an entity's archetype must not contain the specified components. +* `WithPresent()`: To match the query, an entity's archetype must contain the specified components (whether or not they are enabled). For example, the following query includes archetypes that contain the `ObjectRotation` and `ObjectRotationSpeed`components, but excludes any archetypes that contain the `Static` component: diff --git a/Unity.Deformations/DeformationComponents.cs b/Unity.Deformations/DeformationComponents.cs index af84e67e..99b553e3 100644 --- a/Unity.Deformations/DeformationComponents.cs +++ b/Unity.Deformations/DeformationComponents.cs @@ -13,7 +13,7 @@ namespace Unity.Deformations public struct BlendShapeWeight : IBufferElementData { /// - /// The weight value of the blend shape. + /// The weight value of the blend shape. The range is from `0.0f` to `100.0f`, where `0.0f` is 0% and `100.0f` is 100%. /// public float Value; } diff --git a/Unity.Entities.PerformanceTests/BufferLookupPerformanceTests.cs b/Unity.Entities.PerformanceTests/BufferLookupPerformanceTests.cs index de445f09..6de6c62f 100644 --- a/Unity.Entities.PerformanceTests/BufferLookupPerformanceTests.cs +++ b/Unity.Entities.PerformanceTests/BufferLookupPerformanceTests.cs @@ -1,223 +1,222 @@ -// using NUnit.Framework; -// using Unity.Collections; -// using Unity.Entities.Tests; -// using Unity.PerformanceTesting; -// -// namespace Unity.Entities.PerformanceTests -// { -// [Category("Performance")] -// partial class BufferLookupPerformanceTests : EntityQueryBuilderTestFixture -// { -// -// enum ScheduleMode -// { -// Parallel, Single, Run -// } -// -// -// partial class TryGetPerformanceSystem : SystemBase -// { -// public bool ReadOnly; -// public ScheduleMode Schedule; -// public bool UseHasComponent; //either use the if(hasComponent...) bufferLookup[entity] path of the tryGetComponent path. -// protected override void OnUpdate() -// { -// if (UseHasComponent) -// RunHasComponent(); -// else -// RunTryGetBuffer(); -// } -// -// private void RunHasComponent() -// { -// if (ReadOnly) -// { -// var lookup = GetBufferLookup(); -// if (Schedule == ScheduleMode.Run) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.HasComponent(data.value1)) -// data.value0 += lookup[data.value1][0].Value3; -// }).Run(); -// } -// else if (Schedule == ScheduleMode.Parallel) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.HasComponent(data.value1)) -// data.value0 += lookup[data.value1][0].Value3; -// }).ScheduleParallel(); -// CompleteDependency(); -// } -// else if (Schedule == ScheduleMode.Single) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.HasComponent(data.value1)) -// data.value0 += lookup[data.value1][0].Value3; -// }).Schedule(); -// CompleteDependency(); -// } -// -// } -// else -// { -// var lookup = GetBufferLookup(false); -// -// if (Schedule == ScheduleMode.Run) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.HasComponent(data.value1)) -// data.value0 = lookup[data.value1][0].Value3; -// }).Run(); -// } -// else if (Schedule == ScheduleMode.Parallel) -// { -// Entities.WithNativeDisableParallelForRestriction(lookup).ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.HasComponent(data.value1)) -// data.value0 = lookup[data.value1][0].Value3; -// }).ScheduleParallel(); -// CompleteDependency(); -// } -// else if (Schedule == ScheduleMode.Single) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.HasComponent(data.value1)) -// data.value0 = lookup[data.value1][0].Value3; -// }).Schedule(); -// CompleteDependency(); -// } -// } -// } -// -// private void RunTryGetBuffer() -// { -// if (ReadOnly) -// { -// var lookup = GetBufferLookup(true); -// if (Schedule == ScheduleMode.Run) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.TryGetBuffer(data.value1, out var buffer)) -// data.value0 += buffer[0].Value3; -// }).Run(); -// } -// else if (Schedule == ScheduleMode.Parallel) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.TryGetBuffer(data.value1, out var buffer)) -// data.value0 += buffer[0].Value3; -// }).ScheduleParallel(); -// CompleteDependency(); -// } -// else if (Schedule == ScheduleMode.Single) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.TryGetBuffer(data.value1, out var buffer)) -// data.value0 += buffer[0].Value3; -// }).Schedule(); -// CompleteDependency(); -// } -// -// } -// else -// { -// var lookup = GetBufferLookup(false); -// if (Schedule == ScheduleMode.Run) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.TryGetBuffer(data.value1, out var buffer)) -// data.value0 = buffer[0].Value3; -// }).Run(); -// } -// else if (Schedule == ScheduleMode.Parallel) -// { -// Entities.WithNativeDisableParallelForRestriction(lookup).ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.TryGetBuffer(data.value1, out var buffer)) -// data.value0 = buffer[0].Value3; -// }).ScheduleParallel(); -// CompleteDependency(); -// } -// else if (Schedule == ScheduleMode.Single) -// { -// Entities.ForEach((ref EcsTestDataEntity data) => -// { -// if(lookup.TryGetBuffer(data.value1, out var buffer)) -// data.value0 = buffer[0].Value3; -// }).Schedule(); -// CompleteDependency(); -// } -// } -// } -// } -// -// void RunHasComponentSystem(bool readOnly, bool useHasComponent, ScheduleMode schedule) -// { -// var system = World.GetOrCreateSystem(); -// var name = (readOnly ? "ReadOnly" : "Write") + "_" + schedule.ToString(); -// Measure.Method(() => -// { -// system.ReadOnly = false; -// system.Schedule = schedule; -// system.UseHasComponent = useHasComponent; -// system.Update(); -// }) -// .SampleGroup(name) -// .MeasurementCount(10) -// .IterationsPerMeasurement(1) -// .WarmupCount(1) -// .Run(); -// } -// -// [Test, Performance] -// [Category("Performance")] // bug: this redundant category here required because our current test runner ignores Category on a fixture for generated test methods -// public void TestHasBufferLookup([Values(10000, 1000000)] int entityCount, [Values] bool useHasComponent) -// { -// -// -// var targetArchetype = m_Manager.CreateArchetype(); -// -// var targetEntities = m_Manager.CreateEntity(targetArchetype, entityCount, World.UpdateAllocator.ToAllocator); -// -// var archetype = m_Manager.CreateArchetype(typeof(EcsTestDataEntity)); -// var entities = m_Manager.CreateEntity(archetype, entityCount, World.UpdateAllocator.ToAllocator); -// -// -// for (int i = 0; i != entityCount;i++) -// m_Manager.SetComponentData(entities[i], new EcsTestDataEntity { value1 = targetEntities[i] }); -// -// //set every other entity with component data "latest" in typeindex. Which happens to be based on insertion order according to a quick debug log -// for (int i = 0; i < entityCount; i += 2) -// { -// var buffer = m_Manager.AddBuffer(targetEntities[i]); -// buffer.Add(new EcsIntElement4 -// { -// Value0 = i, -// Value1 = i + 1, -// Value2 = i + 2, -// Value3 = i + 3, -// }); -// } -// -// targetEntities.Dispose(); -// entities.Dispose(); -// -// RunHasComponentSystem(true,useHasComponent, ScheduleMode.Run); -// RunHasComponentSystem(true,useHasComponent, ScheduleMode.Single); -// RunHasComponentSystem(true,useHasComponent, ScheduleMode.Parallel); -// -// RunHasComponentSystem(false,useHasComponent, ScheduleMode.Run); -// RunHasComponentSystem(false,useHasComponent, ScheduleMode.Single); -// RunHasComponentSystem(false,useHasComponent, ScheduleMode.Parallel); -// } -// } -// } +using System; +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities.Tests; +using Unity.PerformanceTesting; + +namespace Unity.Entities.PerformanceTests +{ + [Category("Performance")] + partial class BufferLookupPerformanceTests : ECSTestsFixture + { + + enum ScheduleMode + { + Parallel, Single, Run + } + + + partial class TryGetPerformanceSystem : SystemBase + { + public bool ReadOnly; + public ScheduleMode Schedule; + public bool UseHasBuffer; //either use the if(hasBuffer...) bufferLookup[entity] path of the tryGetComponent path. + protected override void OnUpdate() + { + if (UseHasBuffer) + RunHasBuffer(); + else + RunTryGetBuffer(); + } + + private void RunHasBuffer() + { + if (ReadOnly) + { + var lookup = GetBufferLookup(); + if (Schedule == ScheduleMode.Run) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.HasBuffer(data.value1)) + data.value0 += lookup[data.value1][0].Value3; + }).Run(); + } + else if (Schedule == ScheduleMode.Parallel) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.HasBuffer(data.value1)) + data.value0 += lookup[data.value1][0].Value3; + }).ScheduleParallel(); + CompleteDependency(); + } + else if (Schedule == ScheduleMode.Single) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.HasBuffer(data.value1)) + data.value0 += lookup[data.value1][0].Value3; + }).Schedule(); + CompleteDependency(); + } + + } + else + { + var lookup = GetBufferLookup(false); + + if (Schedule == ScheduleMode.Run) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.HasBuffer(data.value1)) + data.value0 = lookup[data.value1][0].Value3; + }).Run(); + } + else if (Schedule == ScheduleMode.Parallel) + { + Entities.WithNativeDisableParallelForRestriction(lookup).ForEach((ref EcsTestDataEntity data) => + { + if(lookup.HasBuffer(data.value1)) + data.value0 = lookup[data.value1][0].Value3; + }).ScheduleParallel(); + CompleteDependency(); + } + else if (Schedule == ScheduleMode.Single) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.HasBuffer(data.value1)) + data.value0 = lookup[data.value1][0].Value3; + }).Schedule(); + CompleteDependency(); + } + } + } + + private void RunTryGetBuffer() + { + if (ReadOnly) + { + var lookup = GetBufferLookup(true); + if (Schedule == ScheduleMode.Run) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.TryGetBuffer(data.value1, out var buffer)) + data.value0 += buffer[0].Value3; + }).Run(); + } + else if (Schedule == ScheduleMode.Parallel) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.TryGetBuffer(data.value1, out var buffer)) + data.value0 += buffer[0].Value3; + }).ScheduleParallel(); + CompleteDependency(); + } + else if (Schedule == ScheduleMode.Single) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.TryGetBuffer(data.value1, out var buffer)) + data.value0 += buffer[0].Value3; + }).Schedule(); + CompleteDependency(); + } + + } + else + { + var lookup = GetBufferLookup(false); + if (Schedule == ScheduleMode.Run) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.TryGetBuffer(data.value1, out var buffer)) + data.value0 = buffer[0].Value3; + }).Run(); + } + else if (Schedule == ScheduleMode.Parallel) + { + Entities.WithNativeDisableParallelForRestriction(lookup).ForEach((ref EcsTestDataEntity data) => + { + if(lookup.TryGetBuffer(data.value1, out var buffer)) + data.value0 = buffer[0].Value3; + }).ScheduleParallel(); + CompleteDependency(); + } + else if (Schedule == ScheduleMode.Single) + { + Entities.ForEach((ref EcsTestDataEntity data) => + { + if(lookup.TryGetBuffer(data.value1, out var buffer)) + data.value0 = buffer[0].Value3; + }).Schedule(); + CompleteDependency(); + } + } + } + } + + void RunHasBufferSystem(bool readOnly, bool useHasBuffer, ScheduleMode schedule) + { + var system = World.GetOrCreateSystemManaged(); + var name = (readOnly ? "ReadOnly" : "Write") + "_" + schedule.ToString(); + Measure.Method(() => + { + system.ReadOnly = false; + system.Schedule = schedule; + system.UseHasBuffer = useHasBuffer; + system.Update(); + }) + .SampleGroup(name) + .MeasurementCount(10) + .IterationsPerMeasurement(1) + .WarmupCount(1) + .Run(); + } + + [Test, Performance] + [Category("Performance")] // bug: this redundant category here required because our current test runner ignores Category on a fixture for generated test methods + public void TestHasBufferLookup([Values(10000, 1000000)] int entityCount, [Values] bool useHasBuffer) + { + var targetArchetype = m_Manager.CreateArchetype(); + + var targetEntities = m_Manager.CreateEntity(targetArchetype, entityCount, World.UpdateAllocator.ToAllocator); + + var archetype = m_Manager.CreateArchetype(typeof(EcsTestDataEntity)); + var entities = m_Manager.CreateEntity(archetype, entityCount, World.UpdateAllocator.ToAllocator); + + + for (int i = 0; i != entityCount;i++) + m_Manager.SetComponentData(entities[i], new EcsTestDataEntity { value1 = targetEntities[i] }); + + //set every other entity with component data "latest" in typeindex. Which happens to be based on insertion order according to a quick debug log + for (int i = 0; i < entityCount; i += 2) + { + var buffer = m_Manager.AddBuffer(targetEntities[i]); + buffer.Add(new EcsIntElement4 + { + Value0 = i, + Value1 = i + 1, + Value2 = i + 2, + Value3 = i + 3, + }); + } + + targetEntities.Dispose(); + entities.Dispose(); + + RunHasBufferSystem(true,useHasBuffer, ScheduleMode.Run); + RunHasBufferSystem(true,useHasBuffer, ScheduleMode.Single); + RunHasBufferSystem(true,useHasBuffer, ScheduleMode.Parallel); + + RunHasBufferSystem(false,useHasBuffer, ScheduleMode.Run); + RunHasBufferSystem(false,useHasBuffer, ScheduleMode.Single); + RunHasBufferSystem(false,useHasBuffer, ScheduleMode.Parallel); + } + } +} diff --git a/Unity.Entities.Tests/BufferElementDataTests.cs b/Unity.Entities.Tests/BufferElementDataTests.cs index 91781845..aba9b249 100644 --- a/Unity.Entities.Tests/BufferElementDataTests.cs +++ b/Unity.Entities.Tests/BufferElementDataTests.cs @@ -25,6 +25,27 @@ namespace Unity.Entities.Tests { class BufferElementDataTests : ECSTestsFixture { + public struct EmptyBufferElement : IBufferElementData + { + } + + [Test] + public void EmptyBufferElement_Works() + { + var e = m_Manager.CreateEntity(typeof(EmptyBufferElement)); + var b = m_Manager.GetBuffer(e, isReadOnly: false); + // C# treats empty component types as having size=1 + Assert.AreEqual(128, b.Capacity); + b.Add(new EmptyBufferElement()); + b.Add(new EmptyBufferElement()); + Assert.AreEqual(2, b.Length); + Assert.AreEqual(b[0], b[1]); + // Make sure buffers still work if they exceed the internal capacity + b.Capacity = 256; + Assert.AreEqual(2, b.Length); + Assert.AreEqual(b[0], b[1]); + } + [InternalBufferCapacity(1024 * 1024)] public struct OverSizedCapacity : IBufferElementData { @@ -510,9 +531,24 @@ public void BufferLookup_Works() var intLookup = EmptySystem.GetBufferLookup(); Assert.IsTrue(intLookup.HasBuffer(entityInt)); - Assert.IsFalse(intLookup.HasBuffer(new Entity())); + Assert.IsTrue(intLookup.HasBuffer(entityInt, out var entityExists)); + Assert.IsTrue(entityExists); + Assert.IsTrue(intLookup.EntityExists(entityInt)); Assert.AreEqual(2, intLookup[entityInt][1].Value); + + Assert.IsFalse(intLookup.HasBuffer(new Entity())); + Assert.IsFalse(intLookup.HasBuffer(new Entity(), out entityExists)); + Assert.IsFalse(entityExists); + Assert.IsFalse(intLookup.EntityExists(new Entity())); + + m_Manager.DestroyEntity(entityInt); + intLookup = EmptySystem.GetBufferLookup(); + + Assert.IsFalse(intLookup.HasBuffer(entityInt)); + Assert.IsFalse(intLookup.HasBuffer(entityInt, out entityExists)); + Assert.IsFalse(entityExists); + Assert.IsFalse(intLookup.EntityExists(entityInt)); } [Test] diff --git a/Unity.Entities.Tests/CleanupBufferElementDataTests.cs b/Unity.Entities.Tests/CleanupBufferElementDataTests.cs index 5e8fe965..04feda6b 100644 --- a/Unity.Entities.Tests/CleanupBufferElementDataTests.cs +++ b/Unity.Entities.Tests/CleanupBufferElementDataTests.cs @@ -320,9 +320,32 @@ public void BufferLookup_Works() var intLookup = EmptySystem.GetBufferLookup(); Assert.IsTrue(intLookup.HasBuffer(entityInt)); - Assert.IsFalse(intLookup.HasBuffer(new Entity())); + Assert.IsTrue(intLookup.HasBuffer(entityInt, out var entityExists)); + Assert.IsTrue(entityExists); + Assert.IsTrue(intLookup.EntityExists(entityInt)); Assert.AreEqual(2, intLookup[entityInt][1].Value); + + Assert.IsFalse(intLookup.HasBuffer(new Entity())); + Assert.IsFalse(intLookup.HasBuffer(new Entity(), out entityExists)); + Assert.IsFalse(entityExists); + Assert.IsFalse(intLookup.EntityExists(new Entity())); + + m_Manager.DestroyEntity(entityInt); + intLookup = EmptySystem.GetBufferLookup(); + + Assert.IsTrue(intLookup.HasBuffer(entityInt)); + Assert.IsTrue(intLookup.HasBuffer(entityInt, out entityExists)); + Assert.IsTrue(entityExists); + Assert.IsTrue(intLookup.EntityExists(entityInt)); + + m_Manager.RemoveComponent(entityInt); + intLookup = EmptySystem.GetBufferLookup(); + + Assert.IsFalse(intLookup.HasBuffer(entityInt)); + Assert.IsFalse(intLookup.HasBuffer(entityInt, out entityExists)); + Assert.IsFalse(entityExists); + Assert.IsFalse(intLookup.EntityExists(entityInt)); } [Test] diff --git a/Unity.Entities.Tests/ComponentSystemTests.cs b/Unity.Entities.Tests/ComponentSystemTests.cs index a855f3d5..1d2c23d9 100644 --- a/Unity.Entities.Tests/ComponentSystemTests.cs +++ b/Unity.Entities.Tests/ComponentSystemTests.cs @@ -416,6 +416,7 @@ protected override void OnUpdate() #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG //accessing a potentially stale ComponentLookup before Update() will throw an exception Assert.Throws(() => _lookup1.HasComponent(_entity)); + Assert.Throws(() => _lookup1.HasComponent(_entity, out _)); #endif var lookup2 = GetComponentLookup(); @@ -431,6 +432,9 @@ protected override void OnUpdate() Assert.AreEqual(lookup2.m_Safety, _lookup1.m_Safety); #endif Assert.IsTrue(_lookup1.HasComponent(_entity)); + Assert.IsTrue(_lookup1.HasComponent(_entity, out var entityExists)); + Assert.IsTrue(entityExists); + Assert.IsTrue(_lookup1.EntityExists(_entity)); } } @@ -648,6 +652,17 @@ public void ComponentLookup_TryGetComponent_Works() Assert.AreEqual(1, componentDataB.value); Assert.AreEqual(2, componentDataC.value); + Assert.IsTrue(array.TryGetComponent(entityA, out componentDataA, out var entityAExists)); + Assert.IsTrue(array.TryGetComponent(entityB, out componentDataB, out var entityBExists)); + Assert.IsTrue(array.TryGetComponent(entityC, out componentDataC, out var entityCExists)); + + Assert.AreEqual(0, componentDataA.value); + Assert.AreEqual(1, componentDataB.value); + Assert.AreEqual(2, componentDataC.value); + + Assert.IsTrue(entityAExists); + Assert.IsTrue(entityBExists); + Assert.IsTrue(entityCExists); } struct ComponentLookupContainerJob : IJob @@ -702,6 +717,10 @@ public void ComponentLookup_TryGetComponent_HasTagComponent() var array = m_Manager.GetComponentLookup(); Assert.IsTrue(array.TryGetComponent(entity,out var tagComponent)); Assert.AreEqual(default(EcsTestTag),tagComponent); + Assert.IsTrue(array.TryGetComponent(entity,out tagComponent, out var entityExists)); + Assert.AreEqual(default(EcsTestTag),tagComponent); + Assert.IsTrue(entityExists); + Assert.IsTrue(array.EntityExists(entity)); } [Test] @@ -767,6 +786,28 @@ public void ComponentLookup_TryGetComponent_NoComponent() Assert.AreEqual(componentData, default(EcsTestData)); } + [Test] + public void ComponentLookup_TryGetComponent_InvalidEntity([Values]bool destroy) + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData + { + value = 5 + }); + + if (destroy) + m_Manager.DestroyEntity(entity); + else entity = Entity.Null; + + var array = m_Manager.GetComponentLookup(); + Assert.IsFalse(array.TryGetComponent(entity, out var componentData)); + Assert.AreEqual(componentData, default(EcsTestData)); + Assert.IsFalse(array.TryGetComponent(entity, out componentData, out var entityExists)); + Assert.AreEqual(componentData, default(EcsTestData)); + Assert.IsFalse(entityExists); + Assert.IsFalse(array.EntityExists(entity)); + } + [Test] public void ComponentLookup_TryGetComponent_FullyUpdatesLookupCache() { @@ -863,6 +904,9 @@ public void BufferLookup_HasBuffer_Works() var array = m_Manager.GetBufferLookup(); Assert.IsTrue(array.HasBuffer(entity)); + Assert.IsTrue(array.HasBuffer(entity, out var entityExists)); + Assert.IsTrue(entityExists); + Assert.IsTrue(array.EntityExists(entity)); } [Test] @@ -876,6 +920,10 @@ public void BufferLookup_TryGetBuffer_Works() Assert.IsTrue(array.TryGetBuffer(entity, out var bufferData)); CollectionAssert.AreEqual(new EcsIntElement[] { 0, 1, 2 }, bufferData.ToNativeArray(Allocator.Temp).ToArray()); + Assert.IsTrue(array.TryGetBuffer(entity, out bufferData, out bool entityExists)); + CollectionAssert.AreEqual(new EcsIntElement[] { 0, 1, 2 }, bufferData.ToNativeArray(Allocator.Temp).ToArray()); + Assert.IsTrue(entityExists); + Assert.IsTrue(array.EntityExists(entity)); } [Test] @@ -886,6 +934,39 @@ public void BufferLookup_TryGetBuffer_NoComponent() Assert.IsFalse(array.TryGetBuffer(entity, out var bufferData)); //I can't do an equivalence check to default since equals appears to not be implemented Assert.IsFalse(bufferData.IsCreated); + Assert.IsFalse(array.TryGetBuffer(entity, out bufferData, out bool entityExists)); + Assert.IsFalse(bufferData.IsCreated); + Assert.IsTrue(entityExists); + Assert.IsTrue(array.EntityExists(entity)); + + m_Manager.DestroyEntity(entity); + array = m_Manager.GetBufferLookup(); + + Assert.IsFalse(array.TryGetBuffer(entity, out bufferData, out entityExists)); + Assert.IsFalse(bufferData.IsCreated); + Assert.IsFalse(entityExists); + Assert.IsFalse(array.EntityExists(entity)); + } + + [Test] + public void BufferLookup_TryGetBuffer_InvalidEntity([Values]bool destroy) + { + var entity = m_Manager.CreateEntity(); + m_Manager.AddBuffer(entity); + m_Manager.GetBuffer(entity).AddRange(new NativeArray(new EcsIntElement[] { 0, 1, 2 }, Allocator.Temp)); + var array = m_Manager.GetBufferLookup(); + + if(destroy) m_Manager.DestroyEntity(entity); + else entity = Entity.Null; + array = m_Manager.GetBufferLookup(); + + Assert.IsFalse(array.TryGetBuffer(entity, out var bufferData)); + //I can't do an equivalence check to default since equals appears to not be implemented + Assert.IsFalse(bufferData.IsCreated); + Assert.IsFalse(array.TryGetBuffer(entity, out bufferData, out bool entityExists)); + Assert.IsFalse(bufferData.IsCreated); + Assert.IsFalse(entityExists); + Assert.IsFalse(array.EntityExists(entity)); } [Test] diff --git a/Unity.Entities.Tests/EntityRemapUtilityTests.cs b/Unity.Entities.Tests/EntityRemapUtilityTests.cs index fecb126a..fb4cff0a 100644 --- a/Unity.Entities.Tests/EntityRemapUtilityTests.cs +++ b/Unity.Entities.Tests/EntityRemapUtilityTests.cs @@ -129,45 +129,52 @@ public void HasEntityReferencesManaged_Basic() //shallow types with no recursion //primitive - EntityRemapUtility.HasEntityReferencesManaged(typeof(string),out var entRef, out var blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(string),out var entRef, out var blobRef, out var unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, unityObjectRef); - EntityRemapUtility.HasEntityReferencesManaged(typeof(System.Int32),out entRef, out blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(System.Int32),out entRef, out blobRef, out unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, unityObjectRef); //blob only - EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TypeOverridesBlob),out entRef, out blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TypeOverridesBlob),out entRef, out blobRef, out unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, unityObjectRef); //entity only - EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TypeOverridesEntity),out entRef, out blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TypeOverridesEntity),out entRef, out blobRef, out unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, unityObjectRef); - //blob and entity - EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TypeOverridesBlobEntity),out entRef, out blobRef); + //blob and entity and unityobjref + EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TypeOverridesBlobEntityUnityObject),out entRef, out blobRef, out unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, unityObjectRef); // entity ref in class with managed strings - EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TestEntityInClassWithManagedFields),out entRef, out blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TestEntityInClassWithManagedFields),out entRef, out blobRef, out unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, unityObjectRef); // blob asset ref in class with managed strings - EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TestBlobRefInClassWithManagedFields),out entRef, out blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(TypeManagerTests.TestBlobRefInClassWithManagedFields),out entRef, out blobRef, out unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, unityObjectRef); } public sealed class RecursionA3: IComponentData @@ -180,18 +187,21 @@ public sealed class RecursionA2: IComponentData { public Recursion1LayerBlob blob1; public Recursion1LayerEntity entity1; + public Recursion1LayerUnityObject objectRef; } public sealed class RecursionB2: IComponentData { public Recursion1LayerBlob blob1; public Recursion1LayerEntity entity1; + public Recursion1LayerUnityObject objectRef; } public sealed class RecursionC2: IComponentData { public Recursion1LayerBlob blob1; public Recursion1LayerEntity entity1; + public Recursion1LayerUnityObject objectRef; } public sealed class Recursion1LayerEntity: IComponentData @@ -204,24 +214,31 @@ public sealed class Recursion1LayerBlob: IComponentData TypeManagerTests.TypeOverridesBlob blob; } + public sealed class Recursion1LayerUnityObject : IComponentData + { + TypeManagerTests.TypeOverridesUnityObjectRef unityObjectRef; + } + [Test] public void HasEntityReferencesManaged_Recursion() { - EntityRemapUtility.HasEntityReferencesManaged(typeof(RecursionA2),out var entRef, out var blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(RecursionA2),out var entRef, out var blobRef, out var unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, unityObjectRef); - EntityRemapUtility.HasEntityReferencesManaged(typeof(RecursionA3),out entRef, out blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(RecursionA3),out entRef, out blobRef, out unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, unityObjectRef); - EntityRemapUtility.HasEntityReferencesManaged(typeof(EcsTestManagedDataEntity),out entRef, out blobRef); + EntityRemapUtility.HasEntityReferencesManaged(typeof(EcsTestManagedDataEntity),out entRef, out blobRef, out unityObjectRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.HasRef, entRef); Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, blobRef); - Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, blobRef); + Assert.AreEqual(EntityRemapUtility.HasRefResult.NoRef, unityObjectRef); } diff --git a/Unity.Entities.Tests/EntityTransactionTests.cs b/Unity.Entities.Tests/EntityTransactionTests.cs index 2795048d..3e41f496 100644 --- a/Unity.Entities.Tests/EntityTransactionTests.cs +++ b/Unity.Entities.Tests/EntityTransactionTests.cs @@ -322,6 +322,7 @@ public void BufferLookup_AcquiredBeforeTransaction_Throws() var linkedEntityGroup = m_Manager.GetBufferLookup(); m_Manager.BeginExclusiveEntityTransaction(); Assert.Throws(() => linkedEntityGroup.HasBuffer(c)); + Assert.Throws(() => linkedEntityGroup.HasBuffer(c, out _)); m_Manager.EndExclusiveEntityTransaction(); } @@ -342,6 +343,7 @@ public void BufferLookup_AcquiredFromTransaction_Throws() var transaction = m_Manager.BeginExclusiveEntityTransaction(); var linkedEntityGroup = transaction.EntityManager.GetBufferLookup(); Assert.DoesNotThrow(() => linkedEntityGroup.HasBuffer(c)); + Assert.DoesNotThrow(() => linkedEntityGroup.HasBuffer(c, out _)); m_Manager.EndExclusiveEntityTransaction(); } diff --git a/Unity.Entities.Tests/ForEachCodegen/ForEachBufferAccessTests.cs b/Unity.Entities.Tests/ForEachCodegen/ForEachBufferAccessTests.cs index 5e2844a3..3daa454f 100644 --- a/Unity.Entities.Tests/ForEachCodegen/ForEachBufferAccessTests.cs +++ b/Unity.Entities.Tests/ForEachCodegen/ForEachBufferAccessTests.cs @@ -209,17 +209,19 @@ public void BufferAccessMethodsExpandILPastShortBranchDistance_CausesNoException var a = 0; if (data.value < 100) { + if (GetBufferLookup().HasBuffer(e)) a++; if (GetBufferLookup().HasBuffer(e)) a++; if (GetBufferLookup().HasBuffer(e)) a++; - if (GetBufferLookup().HasBuffer(e)) + bool entityExists; + if (GetBufferLookup().HasBuffer(e, out entityExists)) a++; - if (GetBufferLookup().HasBuffer(e)) + if (GetBufferLookup().HasBuffer(e, out entityExists)) a++; - if (GetBufferLookup().HasBuffer(e)) + if (GetBufferLookup().HasBuffer(e, out entityExists)) a++; } data.value = a; diff --git a/Unity.Entities.Tests/SharedComponentDataTests.cs b/Unity.Entities.Tests/SharedComponentDataTests.cs index 305be672..c7f19a31 100644 --- a/Unity.Entities.Tests/SharedComponentDataTests.cs +++ b/Unity.Entities.Tests/SharedComponentDataTests.cs @@ -1293,5 +1293,19 @@ public unsafe void IRefCounted_IsDisposed_AfterAddedToTwoEntities_AndDeletedBoth Assert.AreEqual(0, RefCount1); world.Dispose(); } + + public struct EmptySharedComponent : ISharedComponentData + { + } + + [Test] + public void EmptySharedComponent_Works() + { + var e = m_Manager.CreateEntity(); + m_Manager.AddSharedComponent(e, new EmptySharedComponent()); + + } + + } } diff --git a/Unity.Entities.Tests/SizeTests.cs b/Unity.Entities.Tests/SizeTests.cs index 47a18601..fa56238e 100644 --- a/Unity.Entities.Tests/SizeTests.cs +++ b/Unity.Entities.Tests/SizeTests.cs @@ -88,11 +88,14 @@ public void SIZ_TagCanAddComponentData() } [Test] - public void SIZ_TagThrowsOnComponentLookup() + public void SIZ_TagAllowedOnComponentLookup() { var entity = m_Manager.CreateEntity(typeof(EcsTestTag)); var fromEntity = m_Manager.GetComponentLookup(); Assert.IsTrue(fromEntity.HasComponent(entity)); + Assert.IsTrue(fromEntity.HasComponent(entity, out var entityExists)); + Assert.IsTrue(entityExists); + Assert.IsTrue(fromEntity.EntityExists(entity)); var res = fromEntity[entity]; Assert.AreEqual(res , default(EcsTestTag)); } diff --git a/Unity.Entities.Tests/TypeManagerTests.cs b/Unity.Entities.Tests/TypeManagerTests.cs index d2c0f16d..3017157e 100644 --- a/Unity.Entities.Tests/TypeManagerTests.cs +++ b/Unity.Entities.Tests/TypeManagerTests.cs @@ -18,14 +18,14 @@ [assembly: RegisterGenericComponentType(typeof(TypeManagerTests.GenericComponent>))] [assembly: RegisterGenericComponentType(typeof(TypeManagerTests.GenericComponent>))] - - -namespace Unity.Entities.Tests + + +namespace Unity.Entities.Tests { partial class TypeManagerTests : ECSTestsFixture { internal struct TestType1 : IComponentData - { + { int empty; } struct TestTypeWithEntity : IComponentData @@ -559,7 +559,7 @@ public void TestGetSystems() var sys = allSystemTypes[i]; var systemTypeIndex = TypeManager.GetSystemTypeIndex();// A group we know will always exist - if(sys == systemTypeIndex) + if(sys == systemTypeIndex) { foundTestSystem = true; break; @@ -708,20 +708,26 @@ public void GetSystemsRespectsCreateBeforeCreateAfter() Assert.Less(indexOfB, indexOfC); } - [TypeManager.TypeOverrides(hasNoEntityReferences: false, hasNoBlobReferences: true)] - public struct TypeOverridesNoBlobUnmanaged : IComponentData + [TypeManager.TypeOverrides(hasNoEntityReferences: false, hasNoBlobReferences: true, hasNoUnityObjectReferences: true)] + public struct TypeOverridesNoBlobNoUnityObjectUnmanaged : IComponentData { public Entity entity; } - [TypeManager.TypeOverrides(hasNoEntityReferences: true, hasNoBlobReferences: false)] - public struct TypeOverridesNoEntityUnmanaged : IComponentData + [TypeManager.TypeOverrides(hasNoEntityReferences: true, hasNoBlobReferences: false, hasNoUnityObjectReferences: true)] + public struct TypeOverridesNoEntityNoUnityObjectUnmanaged : IComponentData { public BlobAssetReference blob; } - [TypeManager.TypeOverrides(hasNoEntityReferences: false, hasNoBlobReferences: false)] - public struct TypeOverridesNoBlobNoEntityUnmanaged : IComponentData + [TypeManager.TypeOverrides(hasNoEntityReferences: true, hasNoBlobReferences: true, hasNoUnityObjectReferences: false)] + public struct TypeOverridesNoEntityNoBlobUnmanaged : IComponentData + { + public UnityObjectRef obj; + } + + [TypeManager.TypeOverrides(hasNoEntityReferences: false, hasNoBlobReferences: false, hasNoUnityObjectReferences: false)] + public struct TypeOverridesNoBlobNoEntityNoUnityObjectUnmanaged : IComponentData { public float data; } @@ -729,16 +735,25 @@ public struct TypeOverridesNoBlobNoEntityUnmanaged : IComponentData [Test] public void TypeOverrideWorks_Unmanaged_ValidTypesDoNotThrow() { - var typeOverridesNoBlobInfo = TypeManager.GetTypeInfo(); - Assert.IsTrue(TypeManager.HasEntityReferences(typeOverridesNoBlobInfo.TypeIndex)); - Assert.IsFalse(typeOverridesNoBlobInfo.HasBlobAssetRefs); + var typeOverridesNoBlobNoUnityObjectInfo = TypeManager.GetTypeInfo(); + Assert.IsTrue(TypeManager.HasEntityReferences(typeOverridesNoBlobNoUnityObjectInfo.TypeIndex)); + Assert.IsFalse(typeOverridesNoBlobNoUnityObjectInfo.HasUnityObjectRefs); + Assert.IsFalse(typeOverridesNoBlobNoUnityObjectInfo.HasBlobAssetRefs); - var typeOverridesNoEntityInfo = TypeManager.GetTypeInfo(); - Assert.IsTrue(typeOverridesNoEntityInfo.HasBlobAssetRefs); + var typeOverridesNoEntityNoUnityObjectInfo = TypeManager.GetTypeInfo(); + Assert.IsTrue(typeOverridesNoEntityNoUnityObjectInfo.HasBlobAssetRefs); + Assert.IsFalse(typeOverridesNoEntityNoUnityObjectInfo.HasUnityObjectRefs); + Assert.IsFalse(TypeManager.HasEntityReferences(typeOverridesNoEntityNoUnityObjectInfo.TypeIndex)); - var typeOverridesNoBlobNoEntityInfo = TypeManager.GetTypeInfo(); - Assert.IsFalse(TypeManager.HasEntityReferences(typeOverridesNoBlobNoEntityInfo.TypeIndex)); - Assert.IsFalse(typeOverridesNoBlobNoEntityInfo.HasBlobAssetRefs); + var typeOverridesNoEntityNoBlobInfo = TypeManager.GetTypeInfo(); + Assert.IsTrue(typeOverridesNoEntityNoBlobInfo.HasUnityObjectRefs); + Assert.IsFalse(typeOverridesNoEntityNoBlobInfo.HasBlobAssetRefs); + Assert.IsFalse(TypeManager.HasEntityReferences(typeOverridesNoEntityNoBlobInfo.TypeIndex)); + + var typeOverridesNoBlobNoEntityNoUnityObjectInfo = TypeManager.GetTypeInfo(); + Assert.IsFalse(TypeManager.HasEntityReferences(typeOverridesNoBlobNoEntityNoUnityObjectInfo.TypeIndex)); + Assert.IsFalse(typeOverridesNoBlobNoEntityNoUnityObjectInfo.HasBlobAssetRefs); + Assert.IsFalse(typeOverridesNoBlobNoEntityNoUnityObjectInfo.HasUnityObjectRefs); } [DisableAutoTypeRegistration] @@ -818,9 +833,6 @@ struct EmptySharedComponent : ISharedComponentData [TestCase(typeof(Shared), @"\bdisabled\b", TestName = "Implements both ISharedComponentData and IEnableableComponent")] [TestCase(typeof(float), @"\b(not .*|in)valid\b", TestName = "Not valid component type")] - - [TestCase(typeof(EmptyBufferComponent), @"\b(is .*|in)valid\b", TestName = "IBufferElementData types cannot be empty")] - [TestCase(typeof(EmptySharedComponent), @"\b(is .*|in)valid\b", TestName = "ISharedComponentData types cannot be empty")] [TestRequiresDotsDebugOrCollectionChecks("Test requires component type safety checks")] public void BuildComponentType_ThrowsArgumentException_WithExpectedFailures(Type type, string keywordPattern) { @@ -830,6 +842,16 @@ public void BuildComponentType_ThrowsArgumentException_WithExpectedFailures(Type ); } + [TestCase(typeof(EmptyBufferComponent), TestName = "IBufferElementData types can be empty")] + [TestCase(typeof(EmptySharedComponent), TestName = "ISharedComponentData types can be empty")] + [TestRequiresDotsDebugOrCollectionChecks("Test requires component type safety checks")] + public void BuildComponentType_DoesNotThrow(Type type) + { + // This was briefly a fatal error in TypeManager initialization, but we decided to relax it; + // empty components are pointless and wasteful, but not actively harmful. + Assert.DoesNotThrow(() => TypeManager.BuildComponentType(type, new TypeManager.BuildComponentCache())); + } + struct UnmanagedSharedComponent : ISharedComponentData { int a; @@ -1081,7 +1103,7 @@ public void TestNestedNativeContainersDoesNotThrow() { Assert.DoesNotThrow(() => { - Assert.IsTrue(TypeManager.BuildComponentType(typeof(NestedNativeContainerComponent), new TypeManager.BuildComponentCache()).TypeIndex.HasNativeContainer); + Assert.IsTrue(TypeManager.BuildComponentType(typeof(NestedNativeContainerComponent), new TypeManager.BuildComponentCache()).TypeIndex.HasNativeContainer); }); } @@ -1163,40 +1185,48 @@ public class TestBlobRefInClassWithManagedFields : IComponentData } [DisableAutoTypeRegistration] - [TypeManager.TypeOverrides(hasNoEntityReferences:true, hasNoBlobReferences:true)] - public sealed class TypeOverridesBlobEntity: IComponentData + [TypeManager.TypeOverrides(hasNoEntityReferences:true, hasNoBlobReferences:true, hasNoUnityObjectReferences:true)] + public sealed class TypeOverridesBlobEntityUnityObject: IComponentData { public Entity entity; public BlobAssetReference blob; + public UnityObjectRef objectRef; } [DisableAutoTypeRegistration] - [TypeManager.TypeOverrides(hasNoEntityReferences:true, hasNoBlobReferences:false)] + [TypeManager.TypeOverrides(hasNoEntityReferences:true, hasNoBlobReferences:false, hasNoUnityObjectReferences:false)] public sealed class TypeOverridesEntity : IComponentData { public Entity entity; } [DisableAutoTypeRegistration] - [TypeManager.TypeOverrides(hasNoEntityReferences:false, hasNoBlobReferences:true)] + [TypeManager.TypeOverrides(hasNoEntityReferences:false, hasNoBlobReferences:true, hasNoUnityObjectReferences:false)] public sealed class TypeOverridesBlob: IComponentData { public BlobAssetReference blob; } - [TypeManager.TypeOverrides(hasNoEntityReferences:false, hasNoBlobReferences:true)] + [DisableAutoTypeRegistration] + [TypeManager.TypeOverrides(hasNoEntityReferences:false, hasNoBlobReferences:false, hasNoUnityObjectReferences:true)] + public sealed class TypeOverridesUnityObjectRef: IComponentData + { + public UnityObjectRef UnityObjectRef; + } + + [TypeManager.TypeOverrides(hasNoEntityReferences:false, hasNoBlobReferences:true, hasNoUnityObjectReferences:false)] public sealed class TypeOverridesNoBlob: IComponentData { public Entity entity; } - [TypeManager.TypeOverrides(hasNoEntityReferences:true, hasNoBlobReferences:false)] + [TypeManager.TypeOverrides(hasNoEntityReferences:true, hasNoBlobReferences:false, hasNoUnityObjectReferences:false)] public sealed class TypeOverridesNoEntity : IComponentData { public BlobAssetReference blob; } - [TypeManager.TypeOverrides(hasNoEntityReferences:false, hasNoBlobReferences:false)] + [TypeManager.TypeOverrides(hasNoEntityReferences:false, hasNoBlobReferences:false, hasNoUnityObjectReferences:false)] public sealed class TypeOverridesNoBlobNoEntity: IComponentData { public string data; @@ -1306,7 +1336,7 @@ public void TypeOverrideWorks_Managed_InvalidTypesThrow() TypeManager.BuildComponentCache cache = new TypeManager.BuildComponentCache(); Assert.Throws(() => { TypeManager.BuildComponentType(typeof(TypeOverridesBlob), cache); }); Assert.Throws(() => { TypeManager.BuildComponentType(typeof(TypeOverridesEntity), cache); }); - Assert.Throws(() => { TypeManager.BuildComponentType(typeof(TypeOverridesBlobEntity), cache); }); + Assert.Throws(() => { TypeManager.BuildComponentType(typeof(TypeOverridesBlobEntityUnityObject), cache); }); } [Test] diff --git a/Unity.Entities.Tests/Unity.Entities.Tests.asmdef b/Unity.Entities.Tests/Unity.Entities.Tests.asmdef index 56c177eb..5cca3c48 100644 --- a/Unity.Entities.Tests/Unity.Entities.Tests.asmdef +++ b/Unity.Entities.Tests/Unity.Entities.Tests.asmdef @@ -25,8 +25,7 @@ "defineConstraints": [ "UNITY_INCLUDE_TESTS" ], - "versionDefines": - [ + "versionDefines": [ { "name": "Unity", "expression": "2022.2.14f1", @@ -36,7 +35,17 @@ "name": "Unity", "expression": "2022.3.11f1", "define": "UNITY_2022_3_11F1_OR_NEWER" + }, + { + "name": "Unity", + "expression": "2022.3.43f1", + "define": "UNITY_2022_3_43F1_OR_NEWER" + }, + { + "name": "Unity", + "expression": "6000.0.16f1", + "define": "UNITY_6000_0_16F1_OR_NEWER" } ], "noEngineReferences": false -} +} \ No newline at end of file diff --git a/Unity.Entities.Tests/UnityObjectRefTests.cs b/Unity.Entities.Tests/UnityObjectRefTests.cs new file mode 100644 index 00000000..60bde10b --- /dev/null +++ b/Unity.Entities.Tests/UnityObjectRefTests.cs @@ -0,0 +1,179 @@ +#if UNITY_EDITOR +using System; +using System.Collections; +using NUnit.Framework; +using Unity.Collections; +using UnityEditor; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Entities.Tests +{ + [Serializable] + public class UnityObjectRefTests : ECSTestsCommonBase + { + private string TempAssetDir; + private string TempAssetPath; + + [OneTimeSetUp] + public void OneTimeSetup() + { + var guid = AssetDatabase.CreateFolder("Assets", nameof(UnityObjectRefTests)); + TempAssetDir = AssetDatabase.GUIDToAssetPath(guid); + TempAssetPath = $"{TempAssetDir}/TempTextAsset.asset"; + var textAsset = new TextAsset("Foo"); + AssetDatabase.CreateAsset(textAsset, TempAssetPath); + AssetDatabase.Refresh(); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + AssetDatabase.DeleteAsset(TempAssetDir); + } + + struct StructWithUnityObjectRef : IComponentData + { + public UnityObjectRef UnityObjectRef; + } + +#if !UNITY_DISABLE_MANAGED_COMPONENTS + class ClassWithUnityObjectRef : IComponentData + { + public UnityObjectRef UnityObjectRef; + } + + struct SharedComponentManagedWithUnityObjectRef : ISharedComponentData, IEquatable + { + public UnityObjectRef UnityObjectRef; + public UnityEngine.Object DummyManagedField; + + public bool Equals(SharedComponentManagedWithUnityObjectRef other) + { + return UnityObjectRef.Equals(other.UnityObjectRef) && Equals(DummyManagedField, other.DummyManagedField); + } + + public override bool Equals(object obj) + { + return obj is SharedComponentManagedWithUnityObjectRef other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(UnityObjectRef, DummyManagedField); + } + } +#endif // !UNITY_DISABLE_MANAGED_COMPONENTS + + struct SharedComponentWithUnityObjectRef : ISharedComponentData + { + public UnityObjectRef UnityObjectRef; + } + + [TypeManager.TypeOverrides(hasNoBlobReferences:true, hasNoEntityReferences:true, hasNoUnityObjectReferences:true, allowForcedNoReferences:true)] + struct StructWithUnityObjectRefOverride : IComponentData + { + public UnityObjectRef UnityObjectRef; + } + +#if (UNITY_2022_3 && UNITY_2022_3_43F1_OR_NEWER) || (UNITY_6000 && UNITY_6000_0_16F1_OR_NEWER) + [UnityTest] + public IEnumerator AssetGC_StructWithUnityObjectRefOverride_AssetReleased() + { + var textAsset = AssetDatabase.LoadAssetAtPath(TempAssetPath); + var instanceID = textAsset.GetInstanceID(); + + using var world = new World("TestWorld"); + var entity = world.EntityManager.CreateEntity(new ComponentType(typeof(StructWithUnityObjectRefOverride))); + world.EntityManager.SetComponentData(entity, new StructWithUnityObjectRefOverride{UnityObjectRef = textAsset}); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + + textAsset = null; + + yield return Resources.UnloadUnusedAssets(); + + Assert.IsFalse(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + } + + [UnityTest] + public IEnumerator AssetGC_StructComponent_AssetNotReleased() + { + var textAsset = AssetDatabase.LoadAssetAtPath(TempAssetPath); + var instanceID = textAsset.GetInstanceID(); + + using var world = new World("TestWorld"); + var entity = world.EntityManager.CreateEntity(new ComponentType(typeof(StructWithUnityObjectRef))); + world.EntityManager.SetComponentData(entity, new StructWithUnityObjectRef{UnityObjectRef = textAsset}); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + + textAsset = null; + + yield return Resources.UnloadUnusedAssets(); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + } + +#if !UNITY_DISABLE_MANAGED_COMPONENTS + [UnityTest] + public IEnumerator AssetGC_ClassComponent_AssetNotReleased() + { + var textAsset = AssetDatabase.LoadAssetAtPath(TempAssetPath); + var instanceID = textAsset.GetInstanceID(); + + using var world = new World("TestWorld"); + var entity = world.EntityManager.CreateEntity(new ComponentType(typeof(ClassWithUnityObjectRef))); + world.EntityManager.SetComponentData(entity, new ClassWithUnityObjectRef{UnityObjectRef = textAsset}); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + + textAsset = null; + + yield return Resources.UnloadUnusedAssets(); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + } + + [UnityTest] + public IEnumerator AssetGC_SharedComponentManaged_AssetNotReleased() + { + var textAsset = AssetDatabase.LoadAssetAtPath(TempAssetPath); + var instanceID = textAsset.GetInstanceID(); + + using var world = new World("TestWorld"); + var entity = world.EntityManager.CreateEntity(new ComponentType(typeof(SharedComponentManagedWithUnityObjectRef))); + world.EntityManager.SetSharedComponentManaged(entity, new SharedComponentManagedWithUnityObjectRef{UnityObjectRef = textAsset}); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + + textAsset = null; + + yield return Resources.UnloadUnusedAssets(); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + } +#endif + + [UnityTest] + public IEnumerator AssetGC_SharedComponent_AssetNotReleased() + { + var textAsset = AssetDatabase.LoadAssetAtPath(TempAssetPath); + var instanceID = textAsset.GetInstanceID(); + + using var world = new World("TestWorld"); + var entity = world.EntityManager.CreateEntity(new ComponentType(typeof(SharedComponentWithUnityObjectRef))); + world.EntityManager.SetSharedComponent(entity, new SharedComponentWithUnityObjectRef{UnityObjectRef = textAsset}); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + + textAsset = null; + + yield return Resources.UnloadUnusedAssets(); + + Assert.IsTrue(AssetDatabase.IsMainAssetAtPathLoaded(TempAssetPath)); + } +#endif // (UNITY_2022_3 && UNITY_2022_3_43F1_OR_NEWER) || (UNITY_6000 && UNITY_6000_0_16F1_OR_NEWER) + } +} +#endif diff --git a/Unity.Entities.Tests/UnityObjectRefTests.cs.meta b/Unity.Entities.Tests/UnityObjectRefTests.cs.meta new file mode 100644 index 00000000..ec636731 --- /dev/null +++ b/Unity.Entities.Tests/UnityObjectRefTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e49672a4a97df13f2a7ed184f457f325 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Diff/EntityDifferComponentChanges.cs b/Unity.Entities/Diff/EntityDifferComponentChanges.cs index 1a312c50..2855b188 100644 --- a/Unity.Entities/Diff/EntityDifferComponentChanges.cs +++ b/Unity.Entities/Diff/EntityDifferComponentChanges.cs @@ -22,7 +22,7 @@ static unsafe partial class EntityDiffer static bool TryGetEntityGuidComponent(EntityComponentStore* ecs, Entity entity, TypeIndex entityGuidTypeIndex, out EntityGuid entityGuid) { entityGuid = default; - if (!ecs->HasComponent(entity, entityGuidTypeIndex)) + if (!ecs->HasComponent(entity, entityGuidTypeIndex, out _)) { return false; } @@ -2278,7 +2278,7 @@ void IVisitPropertyAdapter.Visit(in VisitContextHasComponent(value, TypeManager.GetTypeIndex())) + if (m_EntityComponentStore->HasComponent(value, TypeManager.GetTypeIndex(), out _)) entityGuid = *(EntityGuid*)m_EntityComponentStore->GetComponentDataWithTypeRO(value, TypeManager.GetTypeIndex()); value = new Entity { Index = m_EntityReferencePatchId, Version = -1 }; diff --git a/Unity.Entities/Diff/EntityPatcher.cs b/Unity.Entities/Diff/EntityPatcher.cs index c973df96..0b369784 100644 --- a/Unity.Entities/Diff/EntityPatcher.cs +++ b/Unity.Entities/Diff/EntityPatcher.cs @@ -659,7 +659,7 @@ public void Execute(int index) do { - if (!store->HasComponent(entity, component.TypeIndex)) + if (!store->HasComponent(entity, component.TypeIndex, out _)) { ecb.AddComponent(index, entity, component); } @@ -804,19 +804,22 @@ public void Execute(int index) do { - if (!store->Exists(entity)) + if (!store->HasComponent(entity, component, out var entityExists)) { - EntityDoesNotExist.AddNoResize(new SetComponentError + if (entityExists) { - ComponentType = component, Guid = PackedEntityGuids[packedComponent.PackedEntityIndex] - }); - } - else if (!store->HasComponent(entity, component)) - { - ComponentDoesNotExist.AddNoResize(new SetComponentError + ComponentDoesNotExist.AddNoResize(new SetComponentError + { + ComponentType = component, Guid = PackedEntityGuids[packedComponent.PackedEntityIndex] + }); + } + else { - ComponentType = component, Guid = PackedEntityGuids[packedComponent.PackedEntityIndex] - }); + EntityDoesNotExist.AddNoResize(new SetComponentError + { + ComponentType = component, Guid = PackedEntityGuids[packedComponent.PackedEntityIndex] + }); + } } else { diff --git a/Unity.Entities/EntityCommandBuffer.cs b/Unity.Entities/EntityCommandBuffer.cs index f30c52c9..2de7d34a 100644 --- a/Unity.Entities/EntityCommandBuffer.cs +++ b/Unity.Entities/EntityCommandBuffer.cs @@ -5294,8 +5294,8 @@ static void FixupBufferContents( [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] private static void CheckBufferExistsOnEntity(EntityComponentStore* mgr, Entity entity, EntityComponentCommand* cmd) { - if (!mgr->HasComponent(entity, cmd->ComponentTypeIndex)) - throw new InvalidOperationException("Buffer does not exist on entity, cannot append element."); + if (!mgr->HasComponent(entity, cmd->ComponentTypeIndex, out bool entityExists)) + throw new InvalidOperationException($"Buffer does not exist on entity {entity} (entityExists:{entityExists}), cannot append element."); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] diff --git a/Unity.Entities/EntityComponentStore.cs b/Unity.Entities/EntityComponentStore.cs index 44d61006..28d08794 100644 --- a/Unity.Entities/EntityComponentStore.cs +++ b/Unity.Entities/EntityComponentStore.cs @@ -1303,18 +1303,20 @@ public int GetComponentTypeOrderVersion(TypeIndex typeIndex) return m_ComponentTypeOrderVersion[typeIndex.Index]; } - public bool HasComponent(Entity entity, TypeIndex type) + public bool HasComponent(Entity entity, TypeIndex type, out bool entityExists) { - if (Hint.Unlikely(!Exists(entity))) + entityExists = Exists(entity); + if (Hint.Unlikely(!entityExists)) return false; var archetype = GetArchetype(entity); return ChunkDataUtility.GetIndexInTypeArray(archetype, type) != -1; } - internal bool HasComponent(Entity entity, TypeIndex type, ref LookupCache cache) + internal bool HasComponent(Entity entity, TypeIndex type, ref LookupCache cache, out bool entityExists) { - if (Hint.Unlikely(!Exists(entity))) + entityExists = Exists(entity); + if (Hint.Unlikely(!entityExists)) return false; var archetype = GetArchetype(entity); @@ -1323,9 +1325,10 @@ internal bool HasComponent(Entity entity, TypeIndex type, ref LookupCache cache) return cache.IndexInArchetype != -1; } - public bool HasComponent(Entity entity, ComponentType type) + public bool HasComponent(Entity entity, ComponentType type, out bool entityExists) { - if (Hint.Unlikely(!Exists(entity))) + entityExists = Exists(entity); + if (Hint.Unlikely(!entityExists)) return false; var archetype = GetArchetype(entity); diff --git a/Unity.Entities/EntityComponentStoreCreateDestroyEntities.cs b/Unity.Entities/EntityComponentStoreCreateDestroyEntities.cs index d5d16232..2e8f3a19 100644 --- a/Unity.Entities/EntityComponentStoreCreateDestroyEntities.cs +++ b/Unity.Entities/EntityComponentStoreCreateDestroyEntities.cs @@ -181,7 +181,7 @@ public ChunkIndex GetCleanChunk(Archetype* archetype, SharedComponentValues shar public void InstantiateEntities(Entity srcEntity, Entity* outputEntities, int instanceCount) { - if (HasComponent(srcEntity, m_LinkedGroupType)) + if (HasComponent(srcEntity, m_LinkedGroupType, out _)) { var header = (BufferHeader*)GetComponentDataWithTypeRO(srcEntity, m_LinkedGroupType); var entityPtr = (Entity*)BufferHeader.GetElementPointer(header); diff --git a/Unity.Entities/EntityComponentStoreDebug.cs b/Unity.Entities/EntityComponentStoreDebug.cs index 8b459260..b8a7f496 100644 --- a/Unity.Entities/EntityComponentStoreDebug.cs +++ b/Unity.Entities/EntityComponentStoreDebug.cs @@ -333,10 +333,10 @@ public void AssertValidEntities(Entity* entities, int count) [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] public void AssertEntityHasComponent(Entity entity, ComponentType componentType) { - if (HasComponent(entity, componentType)) + if (HasComponent(entity, componentType, out var entityExists)) return; - if (!Exists(entity)) + if (!entityExists) throw new ArgumentException("The entity does not exist." + AppendDestroyedEntityRecordError(entity)); throw new ArgumentException($"A component with type:{componentType} has not been added to the entity." + AppendRemovedComponentRecordError(entity, componentType)); @@ -348,10 +348,10 @@ public void AssertEntityHasComponent(NativeArray entities, ComponentType for (int i = 0; i < entities.Length; i++) { var entity = entities[i]; - if (HasComponent(entity, componentType)) + if (HasComponent(entity, componentType, out var entityExists)) continue; - if (!Exists(entity)) + if (!entityExists) throw new ArgumentException("The entity does not exist." + AppendDestroyedEntityRecordError(entity)); throw new ArgumentException($"A component with type:{componentType} has not been added to the entity." + AppendRemovedComponentRecordError(entity, componentType)); @@ -367,10 +367,10 @@ public void AssertEntityHasComponent(Entity entity, TypeIndex componentTypeIndex [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] public void AssertEntityHasComponent(Entity entity, TypeIndex componentTypeIndex, ref LookupCache typeLookupCache) { - if (Hint.Likely(HasComponent(entity, componentTypeIndex, ref typeLookupCache))) + if (Hint.Likely(HasComponent(entity, componentTypeIndex, ref typeLookupCache, out var entityExists))) return; - if (Hint.Unlikely(!Exists(entity))) + if (Hint.Unlikely(!entityExists)) throw new ArgumentException("The entity does not exist." + AppendDestroyedEntityRecordError(entity)); throw new ArgumentException($"A component with type:{componentTypeIndex} has not been added to the entity." + AppendRemovedComponentRecordError(entity, ComponentType.FromTypeIndex(componentTypeIndex))); @@ -723,7 +723,7 @@ public void CheckCanAddChunkComponent(NativeArray chunkArray, Co [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] public void AssertCanInstantiateEntities(Entity srcEntity, Entity* outputEntities, int instanceCount) { - if (HasComponent(srcEntity, m_LinkedGroupType)) + if (HasComponent(srcEntity, m_LinkedGroupType, out var entityExists)) { var header = (BufferHeader*)GetComponentDataWithTypeRO(srcEntity, m_LinkedGroupType); var entityPtr = (Entity*)BufferHeader.GetElementPointer(header); @@ -746,7 +746,7 @@ public void AssertCanInstantiateEntities(Entity srcEntity, Entity* outputEntitie } else { - if (!Exists(srcEntity)) + if (!entityExists) throw new ArgumentException("srcEntity is not a valid entity"); var srcArchetype = GetArchetype(srcEntity); diff --git a/Unity.Entities/EntityDataAccess.cs b/Unity.Entities/EntityDataAccess.cs index fe60d53d..b2a3b1c7 100644 --- a/Unity.Entities/EntityDataAccess.cs +++ b/Unity.Entities/EntityDataAccess.cs @@ -1225,7 +1225,7 @@ public void RemoveComponentDuringStructuralChange(NativeArray entities, public bool HasComponent(Entity entity, ComponentType type) { - return EntityComponentStore->HasComponent(entity, type); + return EntityComponentStore->HasComponent(entity, type, out _); } [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] {typeof(BurstCompatibleComponentData)})] @@ -1452,7 +1452,7 @@ public void ReplaceComponentForLinkedEntityGroup(Entity entity, TypeIndex typeIn // Filter the linked entities based on the mask foreach (var e in linkedEntities) { - if (EntityComponentStore->HasComponent(e, typeIndex)) + if (EntityComponentStore->HasComponent(e, typeIndex, out _)) { SetComponentDataRaw(e, typeIndex, data, componentSize); } diff --git a/Unity.Entities/EntityManager.cs b/Unity.Entities/EntityManager.cs index 261d462d..57da88b7 100644 --- a/Unity.Entities/EntityManager.cs +++ b/Unity.Entities/EntityManager.cs @@ -4483,7 +4483,13 @@ public EntityQuery CreateEntityQuery(params ComponentType[] requiredComponents) /// /// Creates a EntityQuery from an EntityQueryDesc. /// - /// A queryDesc identifying a set of component types. + /// + /// **Warning:** The ability to construct an from multiple query descriptions is not + /// a supported workflow. Queries with multiple query descriptions are not guaranteed to function correctly in + /// all contexts. In general, try to create queries with a single description. For more information, refer to + /// [Creating entity queries](xref:systems-entityquery-create). + /// + /// An array of query descsriptions identifying a sets of component types to match. /// The EntityQuery corresponding to the queryDesc. [ExcludeFromBurstCompatTesting("Takes managed array")] public EntityQuery CreateEntityQuery(params EntityQueryDesc[] queriesDesc) @@ -5915,7 +5921,7 @@ internal bool HasComponentRaw(Entity entity, TypeIndex typeIndex) { var access = GetCheckedEntityDataAccess(); var ecs = access->EntityComponentStore; - return ecs->HasComponent(entity, typeIndex); + return ecs->HasComponent(entity, typeIndex, out _); } #endregion diff --git a/Unity.Entities/EntityRemapUtility.cs b/Unity.Entities/EntityRemapUtility.cs index 60f1bbd0..99d57d98 100644 --- a/Unity.Entities/EntityRemapUtility.cs +++ b/Unity.Entities/EntityRemapUtility.cs @@ -152,6 +152,7 @@ public static void CalculateFieldOffsetsUnmanaged(Type type, out bool hasEntityRefs, out bool hasBlobRefs, out bool hasWeakAssetRefs, + out bool hasUnityObjectRefs, ref NativeList entityOffsets, ref NativeList blobOffsets, ref NativeList weakAssetRefOffsets, @@ -164,11 +165,13 @@ public static void CalculateFieldOffsetsUnmanaged(Type type, int entityOffsetsCount = entityOffsets.Length; int blobOffsetsCount = blobOffsets.Length; int weakAssetRefCount = weakAssetRefOffsets.Length; + int unityObjectRefCount = unityObjectRefOffsets.Length; CalculateOffsetsRecurse(ref entityOffsets, ref blobOffsets, ref weakAssetRefOffsets, ref unityObjectRefOffsets, type, 0, cache); hasEntityRefs = entityOffsets.Length != entityOffsetsCount; hasBlobRefs = blobOffsets.Length != blobOffsetsCount; hasWeakAssetRefs = weakAssetRefOffsets.Length != weakAssetRefCount; + hasUnityObjectRefs = unityObjectRefOffsets.Length != unityObjectRefCount; } static bool CalculateOffsetsRecurse(ref NativeList entityOffsets, ref NativeList blobOffsets, ref NativeList weakAssetRefOffsets, ref NativeList unityObjectRefOffsets, Type type, int baseOffset, HashSet noOffsetTypes) @@ -256,15 +259,21 @@ public struct EntityBlobRefResult /// public HasRefResult HasBlobRef; + /// + /// Specifies if there are any references. + /// + public HasRefResult HasUnityObjectRef; + /// /// Initializes and returns an instance of EntityBlobRefResult. /// /// Specifies if there are any references. /// Specifies if there are any references. - public EntityBlobRefResult(HasRefResult hasEntityRef, HasRefResult hasBlobRef) + public EntityBlobRefResult(HasRefResult hasEntityRef, HasRefResult hasBlobRef, HasRefResult hasUnityObjectRef) { this.HasEntityRef = hasEntityRef; this.HasBlobRef = hasBlobRef; + this.HasUnityObjectRef = hasUnityObjectRef; } } @@ -276,19 +285,20 @@ public EntityBlobRefResult(HasRefResult hasEntityRef, HasRefResult hasBlobRef) /// Specifies if the type has any references. /// Map of type to used to accelerate the type recursion. /// The maximum depth for the recursion. - public static void HasEntityReferencesManaged(Type type, out HasRefResult hasEntityReferences, out HasRefResult hasBlobReferences, Dictionary cache = null, int maxDepth = 128) + public static void HasEntityReferencesManaged(Type type, out HasRefResult hasEntityReferences, out HasRefResult hasBlobReferences, out HasRefResult hasUnityObjectReferences, Dictionary cache = null, int maxDepth = 128) { hasEntityReferences = HasRefResult.NoRef; hasBlobReferences = HasRefResult.NoRef; + hasUnityObjectReferences = HasRefResult.NoRef; if (cache == null) cache = new Dictionary(); - ProcessEntityOrBlobReferencesRecursiveManaged(type, ref hasEntityReferences, ref hasBlobReferences, 0, ref cache, maxDepth); + ProcessEntityOrBlobReferencesRecursiveManaged(type, ref hasEntityReferences, ref hasBlobReferences, ref hasUnityObjectReferences, 0, ref cache, maxDepth); } - static void ProcessEntityOrBlobReferencesRecursiveManaged(Type type, ref HasRefResult hasEntityReferences, ref HasRefResult hasBlobReferences, int depth, ref Dictionary cache, int maxDepth = 10) + static void ProcessEntityOrBlobReferencesRecursiveManaged(Type type, ref HasRefResult hasEntityReferences, ref HasRefResult hasBlobReferences, ref HasRefResult hasUnityObjectReferences, int depth, ref Dictionary cache, int maxDepth = 10) { // Avoid deep / infinite recursion @@ -296,6 +306,7 @@ static void ProcessEntityOrBlobReferencesRecursiveManaged(Type type, ref HasRefR { hasEntityReferences = HasRefResult.MayHaveRef; hasBlobReferences = HasRefResult.MayHaveRef; + hasUnityObjectReferences = HasRefResult.MayHaveRef; return; } @@ -305,6 +316,7 @@ static void ProcessEntityOrBlobReferencesRecursiveManaged(Type type, ref HasRefR var result = cache[type]; hasEntityReferences = result.HasEntityRef; hasBlobReferences = result.HasBlobRef; + hasUnityObjectReferences = result.HasUnityObjectRef; return; } @@ -312,11 +324,12 @@ static void ProcessEntityOrBlobReferencesRecursiveManaged(Type type, ref HasRefR HasRefResult localHasEntityRefs = HasRefResult.NoRef; HasRefResult localHasBlobRefs = HasRefResult.NoRef; + HasRefResult localHasUnityObjectRefs = HasRefResult.NoRef; var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (var field in fields) { - if (localHasEntityRefs > 0 && localHasBlobRefs > 0) + if (localHasEntityRefs > 0 && localHasBlobRefs > 0 && localHasUnityObjectRefs > 0) { break; } @@ -345,31 +358,39 @@ static void ProcessEntityOrBlobReferencesRecursiveManaged(Type type, ref HasRefR { localHasBlobRefs = HasRefResult.HasRef; } + else if (fieldType == typeof(UntypedUnityObjectRef)) + { + localHasUnityObjectRefs = HasRefResult.HasRef; + } else if (fieldType.IsValueType || fieldType.IsSealed) { HasRefResult recursiveHasEntityRefs = HasRefResult.NoRef; HasRefResult recursiveHasBlobRefs = HasRefResult.NoRef; + HasRefResult recursiveHasUnityObjectRefs = HasRefResult.NoRef; ProcessEntityOrBlobReferencesRecursiveManaged(fieldType, ref recursiveHasEntityRefs, - ref recursiveHasBlobRefs, depth + 1, ref cache, maxDepth); + ref recursiveHasBlobRefs, ref recursiveHasUnityObjectRefs, depth + 1, ref cache, maxDepth); localHasEntityRefs = localHasEntityRefs > recursiveHasEntityRefs ? localHasEntityRefs : recursiveHasEntityRefs; localHasBlobRefs = localHasBlobRefs > recursiveHasBlobRefs ? localHasBlobRefs : recursiveHasBlobRefs; + localHasUnityObjectRefs = localHasUnityObjectRefs > recursiveHasUnityObjectRefs ? localHasUnityObjectRefs : recursiveHasUnityObjectRefs; } // It is not possible to determine if there are entity references in a polymorphic non-sealed class type else { localHasEntityRefs = HasRefResult.MayHaveRef; localHasBlobRefs = HasRefResult.MayHaveRef; + localHasUnityObjectRefs = HasRefResult.MayHaveRef; } } if(!cache.ContainsKey(type)) - cache[type] = new EntityBlobRefResult(localHasEntityRefs, localHasBlobRefs); + cache[type] = new EntityBlobRefResult(localHasEntityRefs, localHasBlobRefs, localHasUnityObjectRefs); //Definitely seen a reference takes precedence over maybe, which is more cautious than being sure of no refs hasEntityReferences = hasEntityReferences > localHasEntityRefs ? hasEntityReferences : localHasEntityRefs; hasBlobReferences = hasBlobReferences > localHasBlobRefs ? hasBlobReferences : localHasBlobRefs; + hasUnityObjectReferences = hasUnityObjectReferences > localHasUnityObjectRefs ? hasUnityObjectReferences : localHasUnityObjectRefs; } diff --git a/Unity.Entities/ExclusiveEntityTransaction.cs b/Unity.Entities/ExclusiveEntityTransaction.cs index 07ee5af8..0644d84d 100644 --- a/Unity.Entities/ExclusiveEntityTransaction.cs +++ b/Unity.Entities/ExclusiveEntityTransaction.cs @@ -4,14 +4,19 @@ namespace Unity.Entities { /// - /// Provides an interface to safely perform operations from a single worker thread, by temporarily + /// Provides an interface to safely perform a subset of operations from a single worker thread, by temporarily /// giving that thread exclusive access to a 's . /// - /// The intended use case for this feature is to let a worker thread stream Entity data into a staging - /// and perform any necessary post-processing structural changes, prior to moving the final data into the main simulation world - /// using . This lets the main thread continue to safely process the primary world - /// while the new data is loading in, and not require a main-thread sync point until the new Entities are fully loaded - /// and ready to be injected. + /// + /// The intended use case for this interface is to let a worker thread stream Entity data into a staging + /// and perform any necessary post-processing structural changes, prior to moving the final data into the main simulation + /// world using . This lets the main thread continue to safely process + /// the primary world while the new data is loading in, and not require a main-thread sync point until the new + /// Entities are fully loaded and ready to be injected. + /// + /// **Warning:** Only the operations exposed by this struct are supported within the context + /// of an exclusive entity transaction. For more information, refer to [Safety in Entities](xref:concepts-safety). + /// /// /// public unsafe struct ExclusiveEntityTransaction @@ -19,8 +24,15 @@ public unsafe struct ExclusiveEntityTransaction private EntityManager m_Manager; /// - /// Return the entity manager this transaction operates upon + /// Return the entity manager this transaction operates upon. /// + /// + /// Do not use this property to invoke arbitrary methods from an ExclusiveEntityTransaction because it is undefined + /// behavior. Some EntityManager methods involve operations that are not + /// safe to perform from worker threads, or which might work in certain contexts but fail in others. + /// Only the operations exposed by the struct are guaranteed + /// to function correctly. + /// public EntityManager EntityManager => m_Manager; internal ExclusiveEntityTransaction(EntityManager manager) diff --git a/Unity.Entities/IJobEntity.cs b/Unity.Entities/IJobEntity.cs index 4e7465dc..73755075 100644 --- a/Unity.Entities/IJobEntity.cs +++ b/Unity.Entities/IJobEntity.cs @@ -29,6 +29,13 @@ public interface IJobEntityChunkBeginEnd /// Called at the beginning of every chunk iteration in the . /// It also tells whether or not to run `Execute` on the current . /// + /// + /// Note that the value passed to this function is also used to determine + /// which entities in the current chunk should be processed. Since the mask has already been computed, any components + /// which are enabled or disabled in this chunk during OnChunkBegin() will not affect which of the chunk's entities + /// are processed. To enable/disable components during a chunk pre-pass, it's necessary to use + /// to take full control of the chunk iteration logic. + /// /// An object providing access to the entities within a chunk. /// The index of the current chunk within the list of all chunks in all /// archetypes matched by the that the job was run against. diff --git a/Unity.Entities/Internal/InternalCompilerInterface.cs b/Unity.Entities/Internal/InternalCompilerInterface.cs index 60d44648..9d312aee 100644 --- a/Unity.Entities/Internal/InternalCompilerInterface.cs +++ b/Unity.Entities/Internal/InternalCompilerInterface.cs @@ -257,7 +257,7 @@ public static unsafe void WriteComponentData(EntityManager manager, Entity en var sizeOf = UnsafeUtility.SizeOf(); // MemCmp check is necessary to ensure we only write-back the value if we changed it in the lambda (or a called function) if (UnsafeUtility.MemCmp(UnsafeUtility.AddressOf(ref lambdaComponent), UnsafeUtility.AddressOf(ref originalComponent), sizeOf) != 0 && - ecs->HasComponent(entity, typeIndex)) + ecs->HasComponent(entity, typeIndex, out _)) { var ptr = ecs->GetComponentDataWithTypeRW(entity, typeIndex, ecs->GlobalSystemVersion); UnsafeUtility.CopyStructureToPtr(ref lambdaComponent, ptr); diff --git a/Unity.Entities/Iterators/BufferLookup.cs b/Unity.Entities/Iterators/BufferLookup.cs index 40ecfa8e..7ae8ad6e 100644 --- a/Unity.Entities/Iterators/BufferLookup.cs +++ b/Unity.Entities/Iterators/BufferLookup.cs @@ -103,13 +103,25 @@ internal BufferLookup(TypeIndex typeIndex, EntityDataAccess* access, bool isRead /// The entity. /// /// The buffer component of type T for the given entity, if it exists. /// True if the entity has a buffer component of type T, and false if it does not. - public bool TryGetBuffer(Entity entity, out DynamicBuffer bufferData) + public bool TryGetBuffer(Entity entity, out DynamicBuffer bufferData) => TryGetBuffer(entity, out bufferData, out _); + + /// + /// Retrieves the buffer components associated with the specified , if it exists. Then reports if the instance still refers to a valid entity and that it has a + /// buffer component of type T. + /// + /// The entity. + /// The buffer component of type T for the given entity, if it exists. + /// Denotes whether the given entity exists. Use to distinguish entity non-existence + /// from buffer non-existence. + /// True if the entity has a buffer component of type T, and false if it does not. + public bool TryGetBuffer(Entity entity, out DynamicBuffer bufferData, out bool entityExists) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety0); #endif var ecs = m_Access->EntityComponentStore; - if (Hint.Unlikely(!ecs->Exists(entity))) + entityExists = ecs->Exists(entity); + if (Hint.Unlikely(!entityExists)) { bufferData = default; return false; @@ -135,21 +147,49 @@ public bool TryGetBuffer(Entity entity, out DynamicBuffer bufferData) } } + /// + /// Reports whether the specified entity exists. + /// Does not consider whether this buffer exists on the given entity. + /// + /// The referenced entity. + /// True if the entity exists, regardless of whether this entity has the given buffer. + /// + public bool EntityExists(Entity entity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety0); +#endif + var ecs = m_Access->EntityComponentStore; + return ecs->Exists(entity); + } + + /// + /// Reports whether the specified instance still refers to a valid entity and that it has a + /// buffer component of type T. + /// + /// The entity. + /// True if the entity has a buffer component of type T, and false if it does not. Also returns false if + /// the Entity instance refers to an entity that has been destroyed. + public bool HasBuffer(Entity entity) => HasBuffer(entity, out _); + /// /// Reports whether the specified instance still refers to a valid entity and that it has a /// buffer component of type T. /// /// The entity. + /// Denotes whether the given entity exists. Use to distinguish entity non-existence + /// from buffer non-existence. /// True if the entity has a buffer component of type T, and false if it does not. Also returns false if /// the Entity instance refers to an entity that has been destroyed. - public bool HasBuffer(Entity entity) + public bool HasBuffer(Entity entity, out bool entityExists) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety0); #endif var ecs = m_Access->EntityComponentStore; - return ecs->HasComponent(entity, m_TypeIndex, ref m_Cache); + return ecs->HasComponent(entity, m_TypeIndex, ref m_Cache, out entityExists); } + /// Obsolete. Use instead. /// The entity. /// True if the entity has a buffer component of type T, and false if it does not. Also returns false if diff --git a/Unity.Entities/Iterators/ComponentLookup.cs b/Unity.Entities/Iterators/ComponentLookup.cs index e05686ac..f2ab6a3c 100644 --- a/Unity.Entities/Iterators/ComponentLookup.cs +++ b/Unity.Entities/Iterators/ComponentLookup.cs @@ -114,6 +114,22 @@ public void Update(ref SystemState systemState) #endif } + /// + /// Reports whether the specified entity exists. + /// Does not consider whether this component exists on the given entity. + /// + /// The referenced entity. + /// True if the entity exists, regardless of whether this entity has the given component. + /// + public bool EntityExists(Entity entity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); +#endif + var ecs = m_Access->EntityComponentStore; + return ecs->Exists(entity); + } + /// /// Reports whether the specified instance still refers to a valid entity and that it has a /// component of type T. @@ -121,13 +137,24 @@ public void Update(ref SystemState systemState) /// The entity. /// True if the entity has a component of type T, and false if it does not. Also returns false if /// the Entity instance refers to an entity that has been destroyed. - public bool HasComponent(Entity entity) + public bool HasComponent(Entity entity) => HasComponent(entity, out _); + + /// + /// Reports whether the specified instance still refers to a valid entity and that it has a + /// component of type T. + /// + /// The entity. + /// Denotes whether the given entity exists. Use to differentiate an invalid entity + /// from the entity not having the given buffer. + /// True if the entity has a component of type T, and false if it does not. Also returns false if + /// the Entity instance refers to an entity that has been destroyed. + public bool HasComponent(Entity entity, out bool entityExists) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif var ecs = m_Access->EntityComponentStore; - return ecs->HasComponent(entity, m_TypeIndex, ref m_Cache); + return ecs->HasComponent(entity, m_TypeIndex, ref m_Cache, out entityExists); } /// @@ -143,7 +170,7 @@ public bool HasComponent(SystemHandle system) AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif var ecs = m_Access->EntityComponentStore; - return ecs->HasComponent(system.m_Entity, m_TypeIndex, ref m_Cache); + return ecs->HasComponent(system.m_Entity, m_TypeIndex, ref m_Cache, out _); } /// @@ -151,11 +178,12 @@ public bool HasComponent(SystemHandle system) /// component of type T. /// /// The entity. - /// /// The component of type T for the given entity, if it exists. + /// The component of type T for the given entity, if it exists. + /// Denotes whether the given entity exists. Use to differentiate an invalid entity + /// from the entity not having the given buffer. /// True if the entity has a component of type T, and false if it does not. - public bool TryGetComponent(Entity entity, out T componentData) + public bool TryGetComponent(Entity entity, out T componentData, out bool entityExists) { - #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif @@ -164,10 +192,11 @@ public bool TryGetComponent(Entity entity, out T componentData) if (m_IsZeroSized != 0) { componentData = default; - return ecs->HasComponent(entity, m_TypeIndex, ref m_Cache); + return ecs->HasComponent(entity, m_TypeIndex, ref m_Cache, out entityExists); } - if (Hint.Unlikely(!ecs->Exists(entity))) + entityExists = ecs->Exists(entity); + if (Hint.Unlikely(!entityExists)) { componentData = default; return false; @@ -182,6 +211,15 @@ public bool TryGetComponent(Entity entity, out T componentData) return true; } + /// + /// Retrieves the component associated with the specified , if it exists. Then reports if the instance still refers to a valid entity and that it has a + /// component of type T. + /// + /// The entity. + /// The component of type T for the given entity, if it exists. + /// True if the entity has a component of type T, and false if it does not. + public bool TryGetComponent(Entity entity, out T componentData) => TryGetComponent(entity, out componentData, out _); + /// /// Reports whether any of IComponentData components of the type T, in the chunk containing the /// specified , could have changed. diff --git a/Unity.Entities/Iterators/EntityQueryBuilder.cs b/Unity.Entities/Iterators/EntityQueryBuilder.cs index 8a8a2ab5..b174b68e 100644 --- a/Unity.Entities/Iterators/EntityQueryBuilder.cs +++ b/Unity.Entities/Iterators/EntityQueryBuilder.cs @@ -1717,6 +1717,10 @@ internal EntityQueryBuilder WithPresent(ComponentType* componentTypes, int count /// Add an additional query description to a single EntityQuery. /// /// + /// The ability to construct an from multiple query descriptions is preserved for backwards + /// compatibility, but the use of this feature is strongly discouraged in user projects. Queries with multiple + /// query descriptions are not guaranteed to function correctly in all contexts. + /// /// The resulting EntityQuery will match all entities matched by any individual query description. In terms of /// set theory, the query matches the union of its query descriptions, not the intersection. /// diff --git a/Unity.Entities/ManagedComponentStore.cs b/Unity.Entities/ManagedComponentStore.cs index 7eded4b1..16ed630b 100644 --- a/Unity.Entities/ManagedComponentStore.cs +++ b/Unity.Entities/ManagedComponentStore.cs @@ -39,7 +39,7 @@ internal unsafe class ManagedComponentStore UnsafeParallelMultiHashMap m_HashLookup = new UnsafeParallelMultiHashMap(128, Allocator.Persistent); - List m_SharedComponentData = new List(); + internal List m_SharedComponentData = new List(); struct SharedComponentInfo { diff --git a/Unity.Entities/Serialization/UnityObjectRef.cs b/Unity.Entities/Serialization/UnityObjectRef.cs index 6ff63cb2..dd8e8001 100644 --- a/Unity.Entities/Serialization/UnityObjectRef.cs +++ b/Unity.Entities/Serialization/UnityObjectRef.cs @@ -2,7 +2,12 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Profiling; +using Unity.Properties; +using UnityEditor; using UnityEngine; +using UnityEngine.Profiling; using Object = UnityEngine.Object; namespace Unity.Entities @@ -53,6 +58,272 @@ public int Add(int instanceId) } } +#if UNITY_EDITOR + [UnityEditor.InitializeOnLoad] +#endif + internal static class UnityObjectRefUtility + { +#if UNITY_EDITOR + static UnityObjectRefUtility() + { + EditorInitializeOnLoadMethod(); + } +#endif + + unsafe class ManagedUnityObjectRefCollector: + Properties.IPropertyBagVisitor, + Properties.IPropertyVisitor, + ITypedVisit, + IDisposable + { + UnsafeHashSet m_References; + UnsafeHashSet* m_InstanceIDRefs; + + public ManagedUnityObjectRefCollector(UnsafeHashSet* instanceIDRefs) + { + m_InstanceIDRefs = instanceIDRefs; + m_References = new UnsafeHashSet(256, Allocator.Temp); + } + + IPropertyBag GetPropertyBag(object obj) + { + var properties = PropertyBag.GetPropertyBag(obj.GetType()); + return properties; + } + + public void CollectReferences(ref object obj) + { + GetPropertyBag(obj).Accept(this, ref obj); + } + + void Properties.IPropertyBagVisitor.Visit(Properties.IPropertyBag properties, ref TContainer container) + { + foreach (var property in properties.GetProperties(ref container)) + ((Properties.IPropertyAccept)property).Accept(this, ref container); + } + + void Properties.IPropertyVisitor.Visit(Properties.Property property, + ref TContainer container) + { + if (Properties.TypeTraits.CanBeNull || !Properties.TypeTraits.IsValueType) + return; + + var value = property.GetValue(ref container); + + if (this is ITypedVisit typed) + { + typed.Visit(property, ref container, ref value); + return; + } + + Properties.PropertyContainer.Accept(this, ref value); + } + + void ITypedVisit.Visit(Properties.Property property, ref TContainer container, ref UntypedUnityObjectRef value) + { + m_InstanceIDRefs->Add(value.instanceId); + } + + public void Dispose() + { + m_References.Dispose(); + } + } + + private static unsafe void AddInstanceIDRefsFromComponent(byte* componentData, TypeManager.EntityOffsetInfo* unityObjectRefOffsets, int unityObjectRefCount, UnsafeHashSet* instanceIDRefs) + { + for (int i = 0; i < unityObjectRefCount; ++i) + { + var unityObjectRefOffset = unityObjectRefOffsets[i].Offset; + var unityObjectRefPtr = (UntypedUnityObjectRef*)(componentData + unityObjectRefOffset); + instanceIDRefs->Add(unityObjectRefPtr->instanceId); + } + } + + static unsafe void AddInstanceIDRefsFromChunk(Archetype* archetype, int entityCount, byte* chunkBuffer, UnsafeHashSet* instanceIDRefs) + { + var typeCount = archetype->TypesCount; + + for (var unordered_ti = 0; unordered_ti < typeCount; ++unordered_ti) + { + var ti = archetype->TypeMemoryOrderIndexToIndexInArchetype[unordered_ti]; + var type = archetype->Types[ti]; + if (type.IsZeroSized || type.IsManagedComponent || type.TypeIndex == ManagedComponentStore.CompanionLinkTypeIndex || type.TypeIndex == ManagedComponentStore.CompanionLinkTransformTypeIndex) + continue; + + ref readonly var ct = ref TypeManager.GetTypeInfo(type.TypeIndex); + var unityObjectRefCount = ct.UnityObjectRefOffsetCount; + + if (unityObjectRefCount == 0) + continue; + + var unityObjectRefOffsets = TypeManager.GetUnityObjectRefOffsets(ct); + int subArrayOffset = archetype->Offsets[ti]; + byte* componentArrayStart = chunkBuffer + subArrayOffset; + + if (type.IsBuffer) + { + BufferHeader* header = (BufferHeader*)componentArrayStart; + int strideSize = archetype->SizeOfs[ti]; + var elementSize = ct.ElementSize; + + for (int bi = 0; bi < entityCount; ++bi) + { + var bufferStart = BufferHeader.GetElementPointer(header); + var bufferEnd = bufferStart + header->Length * elementSize; + for (var componentData = bufferStart; componentData < bufferEnd; componentData += elementSize) + { + AddInstanceIDRefsFromComponent(componentData, unityObjectRefOffsets,unityObjectRefCount, instanceIDRefs); + } + + header = (BufferHeader*)((byte*)header + strideSize); + } + } + else + { + int size = archetype->SizeOfs[ti]; + byte* end = componentArrayStart + size * entityCount; + for (var componentData = componentArrayStart; componentData < end; componentData += size) + { + AddInstanceIDRefsFromComponent(componentData, unityObjectRefOffsets, unityObjectRefCount, instanceIDRefs); + } + } + } + } + + static unsafe void AddInstanceIDRefsFromAllChunks(Archetype* archetype, UnsafeHashSet* instanceIDRefs) + { + for (var chunkIndex = 0; chunkIndex < archetype->Chunks.Count; chunkIndex++) + { + var chunk = archetype->Chunks[chunkIndex]; + var chunkPtr = chunk.GetPtr(); + var chunkBuffer = chunkPtr->Buffer; + + AddInstanceIDRefsFromChunk(archetype, chunk.Count, chunkBuffer, instanceIDRefs); + } + } + + static unsafe void AddInstanceIDRefsFromUnmanagedSharedComponents(EntityDataAccess* access, Archetype* archetype, UnsafeHashSet* instanceIDRefs) + { + int numSharedComponents = archetype->NumSharedComponents; + for (int iType = 0; iType < numSharedComponents; iType++) + { + var sharedComponents = archetype->Chunks.GetSharedComponentValueArrayForType(iType); + + for (int chunkIndex = 0; chunkIndex < archetype->Chunks.Count; chunkIndex++) + { + var sharedComponentIndex = sharedComponents[chunkIndex]; + + if (EntityComponentStore.IsUnmanagedSharedComponentIndex(sharedComponentIndex)) + { + var typeIndex = EntityComponentStore.GetComponentTypeFromSharedComponentIndex(sharedComponentIndex); + ref readonly var typeInfo = ref TypeManager.GetTypeInfo(typeIndex); + + var unityObjectRefCount = typeInfo.UnityObjectRefOffsetCount; + if (unityObjectRefCount == 0) + continue; + + var unityObjectRefOffsets = TypeManager.GetUnityObjectRefOffsets(typeInfo); + var dataPtr = (byte*)access->EntityComponentStore->GetSharedComponentDataAddr_Unmanaged(sharedComponentIndex, typeIndex); + AddInstanceIDRefsFromComponent(dataPtr, unityObjectRefOffsets, unityObjectRefCount, instanceIDRefs); + } + } + } + } + +#if !UNITY_DISABLE_MANAGED_COMPONENTS + static unsafe void AddInstanceIDRefsFromManagedComponents(EntityDataAccess* access, UnsafeHashSet* instanceIDRefs) + { + using var managedObjectRefWalker = new ManagedUnityObjectRefCollector(instanceIDRefs); + + // Managed components + s_AddFromManagedComponents.Begin(); + for (int i = 0; i < access->ManagedComponentStore.m_ManagedComponentData.Length; i++) + { + var managedComponent = access->ManagedComponentStore.m_ManagedComponentData[i]; + if (managedComponent == null) + continue; + + ref readonly var typeInfo = ref TypeManager.GetTypeInfo(TypeManager.GetTypeIndex(managedComponent.GetType())); + + if (!typeInfo.HasUnityObjectRefs || typeInfo.TypeIndex == ManagedComponentStore.CompanionReferenceTypeIndex ) + continue; + + managedObjectRefWalker.CollectReferences(ref managedComponent); + } + s_AddFromManagedComponents.End(); + + // Managed shared components + s_AddFromManagedSharedComponents.Begin(); + int sharedComponentCount = access->ManagedComponentStore.GetSharedComponentCount(); + for (int i = 0; i < sharedComponentCount; i++) + { + var managedSharedComponent = access->ManagedComponentStore.m_SharedComponentData[i]; + if (managedSharedComponent == null) + continue; + + ref readonly var typeInfo = ref TypeManager.GetTypeInfo(TypeManager.GetTypeIndex(managedSharedComponent.GetType())); + + if (!typeInfo.HasUnityObjectRefs) + continue; + + managedObjectRefWalker.CollectReferences(ref managedSharedComponent); + } + s_AddFromManagedSharedComponents.End(); + } +#endif + + private static ProfilerMarker s_AddFromChunks = new ProfilerMarker("AddFromChunks"); + private static ProfilerMarker s_AddFromUnmanagedSharedComponents = new ProfilerMarker("AddFromUnmanagaedSharedComponents"); + private static ProfilerMarker s_AddFromManagedComponents = new ProfilerMarker("AddFromManagedComponents"); + private static ProfilerMarker s_AddFromManagedSharedComponents = new ProfilerMarker("AddFromManagedSharedComponents"); + + static unsafe void AdditionalRootsHandlerDelegate(IntPtr state) + { + using var instanceIDRefs = new UnsafeHashSet(256, Allocator.Temp); + foreach (var world in World.s_AllWorlds) + { + var access = world.EntityManager.GetCheckedEntityDataAccessExclusive(); + access->CompleteAllTrackedJobs(); + for (var i = 0; i < access->EntityComponentStore->m_Archetypes.Length; ++i) + { + var archetype = access->EntityComponentStore->m_Archetypes.Ptr[i]; + + if (!archetype->HasUnityObjectRefs) + continue; + + s_AddFromChunks.Begin(); + AddInstanceIDRefsFromAllChunks(archetype, &instanceIDRefs); + s_AddFromChunks.End(); + s_AddFromUnmanagedSharedComponents.Begin(); + AddInstanceIDRefsFromUnmanagedSharedComponents(access, archetype, &instanceIDRefs); + s_AddFromUnmanagedSharedComponents.End(); + #if !UNITY_DISABLE_MANAGED_COMPONENTS + AddInstanceIDRefsFromManagedComponents(access, &instanceIDRefs); + #endif + } + } + + if (instanceIDRefs.Count == 0) + return; + + using var instanceIDs = instanceIDRefs.ToNativeArray(Allocator.Temp); +#if (UNITY_2022_3 && UNITY_2022_3_43F1_OR_NEWER) || (UNITY_6000 && UNITY_6000_0_16F1_OR_NEWER) + ResourcesAPIInternal.EntitiesAssetGC.MarkInstanceIDsAsRoot((IntPtr)instanceIDs.GetUnsafePtr(), instanceIDs.Length, state); +#endif + } + +#if !UNITY_EDITOR + [RuntimeInitializeOnLoadMethod] +#endif + static void EditorInitializeOnLoadMethod() + { + #if (UNITY_2022_3 && UNITY_2022_3_43F1_OR_NEWER) || (UNITY_6000 && UNITY_6000_0_16F1_OR_NEWER) + ResourcesAPIInternal.EntitiesAssetGC.RegisterAdditionalRootsHandler(AdditionalRootsHandlerDelegate); + #endif + } + } + [Serializable] [StructLayout(LayoutKind.Sequential)] internal struct UntypedUnityObjectRef : IEquatable @@ -77,10 +348,12 @@ public override int GetHashCode() } /// - /// A utility structure that stores a reference of an for the BakingSystem to process in an unmanaged component. + /// A utility structure that stores a reference of an for Entities. Allows references to be stored on unmanaged component. /// /// Type of the Object that is going to be referenced by UnityObjectRef. - /// Stores the Object's instance ID. This means that the reference is only valid during the baking process. + /// Stores the Object's instance ID. Will also serialize asset references in SubScenes the same way managed components do with direct references to . This is the recommended way to store references to Unity assets in Entities as it remains unmanaged. + /// Serialization is supported on and + /// Unity assets referenced in this way will be prevented from being collect by Asset Garbage Collection (such as calling ). [Serializable] [StructLayout(LayoutKind.Sequential)] public struct UnityObjectRef : IEquatable> diff --git a/Unity.Entities/SourceGenerators/AspectGenerator.dll b/Unity.Entities/SourceGenerators/AspectGenerator.dll index 487aea0b..fedf8b0e 100644 Binary files a/Unity.Entities/SourceGenerators/AspectGenerator.dll and b/Unity.Entities/SourceGenerators/AspectGenerator.dll differ diff --git a/Unity.Entities/SourceGenerators/AspectGenerator.pdb b/Unity.Entities/SourceGenerators/AspectGenerator.pdb index c2fa66e8..4a92d9d9 100644 Binary files a/Unity.Entities/SourceGenerators/AspectGenerator.pdb and b/Unity.Entities/SourceGenerators/AspectGenerator.pdb differ diff --git a/Unity.Entities/SourceGenerators/Common.dll b/Unity.Entities/SourceGenerators/Common.dll index 3840daf2..a925b34a 100644 Binary files a/Unity.Entities/SourceGenerators/Common.dll and b/Unity.Entities/SourceGenerators/Common.dll differ diff --git a/Unity.Entities/SourceGenerators/Common.pdb b/Unity.Entities/SourceGenerators/Common.pdb index 8bd45c34..c3293739 100644 Binary files a/Unity.Entities/SourceGenerators/Common.pdb and b/Unity.Entities/SourceGenerators/Common.pdb differ diff --git a/Unity.Entities/SourceGenerators/JobEntityGenerator.dll b/Unity.Entities/SourceGenerators/JobEntityGenerator.dll index c43b254d..b5358a17 100644 Binary files a/Unity.Entities/SourceGenerators/JobEntityGenerator.dll and b/Unity.Entities/SourceGenerators/JobEntityGenerator.dll differ diff --git a/Unity.Entities/SourceGenerators/JobEntityGenerator.pdb b/Unity.Entities/SourceGenerators/JobEntityGenerator.pdb index 477641c4..98fda164 100644 Binary files a/Unity.Entities/SourceGenerators/JobEntityGenerator.pdb and b/Unity.Entities/SourceGenerators/JobEntityGenerator.pdb differ diff --git a/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/IjeSchedulingSyntaxWalker.cs b/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/IjeSchedulingSyntaxWalker.cs index 7080efe3..77f91121 100644 --- a/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/IjeSchedulingSyntaxWalker.cs +++ b/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/IjeSchedulingSyntaxWalker.cs @@ -50,14 +50,12 @@ public class IjeSchedulingSyntaxWalker : CSharpSyntaxWalker, IModuleSyntaxWalker private string _userDefinedDependency; private ObjectCreationExpressionSyntax _jobEntityInstanceCreationSyntax; - internal IjeSchedulingSyntaxWalker(ref SystemDescription systemDescription, IReadOnlyCollection jobEntityInfos) + internal IjeSchedulingSyntaxWalker(ref SystemDescription systemDescription, Dictionary jobEntityInfos) : base(SyntaxWalkerDepth.Trivia) { _uniqueId = 0; _systemDescription = systemDescription; - _schedulingInvocationNodes = - jobEntityInfos.GroupBy(info => info.Candidate.Invocation) - .ToDictionary(group => group.Key, group => group.Single()); + _schedulingInvocationNodes = jobEntityInfos; _schedulingArgsInnerWriter = new StringWriter(); _schedulingArgsWriter = new IndentedTextWriter(_schedulingArgsInnerWriter); diff --git a/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/JobEntityDescription.cs b/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/JobEntityDescription.cs index c823e811..4bb77450 100644 --- a/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/JobEntityDescription.cs +++ b/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/JobEntityDescription.cs @@ -120,6 +120,14 @@ JobEntityParam CreateJobEntityParam(IParameterSymbol parameterSymbol, bool perfo var typeArgSymbol = namedTypeSymbol.TypeArguments.Single(); var fullName = namedTypeSymbol.ConstructedFrom.ToFullName(); + // If user has passed in a type that is not accessible then it likely means they have a compile error in their code + // since the compile error will be shown in the editor, we can safely ignore reporting it ourselves + if (typeArgSymbol.DeclaredAccessibility == Accessibility.NotApplicable) + { + Invalid = true; + return null; + } + switch (fullName) { // Dynamic Buffer diff --git a/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/JobEntityModule.cs b/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/JobEntityModule.cs index f21e5f95..1b3e35f5 100644 --- a/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/JobEntityModule.cs +++ b/Unity.Entities/SourceGenerators/Source~/JobEntityGenerator/JobEntityModule.cs @@ -59,12 +59,12 @@ public void OnReceiveSyntaxNode(SyntaxNode node, Dictionary(candidates.Count); + var validCandidates = new Dictionary(candidates.Count); foreach (var candidate in candidates) if (JobEntityInstanceInfo.TryCreate(ref systemDescription, candidate, out var knownJobEntityInfo)) { - validCandidates.Add(knownJobEntityInfo); + validCandidates[candidate.Invocation] = knownJobEntityInfo; systemDescription.CandidateNodes.Add( key: candidate.Invocation, value: new CandidateSyntax(CandidateType.IJobEntity, CandidateFlags.None, candidate.Invocation)); diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.Common.dll b/Unity.Entities/SourceGenerators/SystemGenerator.Common.dll index eed31056..ea0d6220 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.Common.dll and b/Unity.Entities/SourceGenerators/SystemGenerator.Common.dll differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.Common.pdb b/Unity.Entities/SourceGenerators/SystemGenerator.Common.pdb index ce19c471..d9953349 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.Common.pdb and b/Unity.Entities/SourceGenerators/SystemGenerator.Common.pdb differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.EntityQueryBulkOperations.dll b/Unity.Entities/SourceGenerators/SystemGenerator.EntityQueryBulkOperations.dll index d5194c9f..72821307 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.EntityQueryBulkOperations.dll and b/Unity.Entities/SourceGenerators/SystemGenerator.EntityQueryBulkOperations.dll differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.EntityQueryBulkOperations.pdb b/Unity.Entities/SourceGenerators/SystemGenerator.EntityQueryBulkOperations.pdb index 4da234f0..e8d18861 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.EntityQueryBulkOperations.pdb and b/Unity.Entities/SourceGenerators/SystemGenerator.EntityQueryBulkOperations.pdb differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.LambdaJobs.dll b/Unity.Entities/SourceGenerators/SystemGenerator.LambdaJobs.dll index d2fefdf9..60ceffe3 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.LambdaJobs.dll and b/Unity.Entities/SourceGenerators/SystemGenerator.LambdaJobs.dll differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.LambdaJobs.pdb b/Unity.Entities/SourceGenerators/SystemGenerator.LambdaJobs.pdb index c0845cdf..b11af3f0 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.LambdaJobs.pdb and b/Unity.Entities/SourceGenerators/SystemGenerator.LambdaJobs.pdb differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.Query.dll b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.Query.dll index 755d1ebb..118530d5 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.Query.dll and b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.Query.dll differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.Query.pdb b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.Query.pdb index 12d7c285..270242c7 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.Query.pdb and b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.Query.pdb differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.QueryBuilder.dll b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.QueryBuilder.dll index 5c1b2fbf..a05fd3cf 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.QueryBuilder.dll and b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.QueryBuilder.dll differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.QueryBuilder.pdb b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.QueryBuilder.pdb index e7ec3f82..08544459 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.QueryBuilder.pdb and b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.QueryBuilder.pdb differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.dll b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.dll index ec774591..cd0ee075 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.dll and b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.dll differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.pdb b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.pdb index 1a3665b8..1c965f9b 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.pdb and b/Unity.Entities/SourceGenerators/SystemGenerator.SystemAPI.pdb differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.dll b/Unity.Entities/SourceGenerators/SystemGenerator.dll index 7218ab76..2ef96d7b 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.dll and b/Unity.Entities/SourceGenerators/SystemGenerator.dll differ diff --git a/Unity.Entities/SourceGenerators/SystemGenerator.pdb b/Unity.Entities/SourceGenerators/SystemGenerator.pdb index 9b9adc30..5c7e1607 100644 Binary files a/Unity.Entities/SourceGenerators/SystemGenerator.pdb and b/Unity.Entities/SourceGenerators/SystemGenerator.pdb differ diff --git a/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.CodeFixes.dll b/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.CodeFixes.dll index c7ecc68a..a218e2ab 100644 Binary files a/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.CodeFixes.dll and b/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.CodeFixes.dll differ diff --git a/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.CodeFixes.pdb b/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.CodeFixes.pdb index 7bd4e621..df4f0311 100644 Binary files a/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.CodeFixes.pdb and b/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.CodeFixes.pdb differ diff --git a/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.dll b/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.dll index dcc0416e..656ce624 100644 Binary files a/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.dll and b/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.dll differ diff --git a/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.pdb b/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.pdb index cb9a881c..0db901fe 100644 Binary files a/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.pdb and b/Unity.Entities/SourceGenerators/Unity.Entities.Analyzer.pdb differ diff --git a/Unity.Entities/Types/TypeManager.cs b/Unity.Entities/Types/TypeManager.cs index 57d1f4e3..74171f29 100644 --- a/Unity.Entities/Types/TypeManager.cs +++ b/Unity.Entities/Types/TypeManager.cs @@ -434,16 +434,38 @@ public class TypeOverridesAttribute : Attribute /// Otherwise, the value will only be zero if the component actually has a blob reference. /// public bool HasNoBlobReferences; + /// + /// Configures the TypeManager to set the component type's to zero. + /// + /// + /// You can use this to bypass the TypeManager check and force the component type's reference to zero. + /// Set to `false` to set the component type's to zero. Set to `true` to set the value to zero only when the component has a Unity object reference. + /// + public bool HasNoUnityObjectReferences; + + // Use this to bypass the TypeManager check which complains if you say no references but it actually has them + internal bool AllowForceNoReferences; /// /// /// /// Set to true to ignore entity references in type. /// Set to true to ignore blob asset references in type. - public TypeOverridesAttribute(bool hasNoEntityReferences, bool hasNoBlobReferences) + /// Set to true to ignore unity object references in type. + public TypeOverridesAttribute(bool hasNoEntityReferences, bool hasNoBlobReferences, bool hasNoUnityObjectReferences = false) { HasNoEntityReferences = hasNoEntityReferences; HasNoBlobReferences = hasNoBlobReferences; + HasNoUnityObjectReferences = hasNoUnityObjectReferences; + AllowForceNoReferences = false; + } + + internal TypeOverridesAttribute(bool hasNoEntityReferences, bool hasNoBlobReferences, bool hasNoUnityObjectReferences, bool allowForcedNoReferences) + { + HasNoEntityReferences = hasNoEntityReferences; + HasNoBlobReferences = hasNoBlobReferences; + HasNoUnityObjectReferences = hasNoUnityObjectReferences; + AllowForceNoReferences = allowForcedNoReferences; } } @@ -810,7 +832,7 @@ public TypeInfo(int typeIndex, TypeCategory category, int entityOffsetCount, int int alignmentInBytes, int maximumChunkCapacity, int writeGroupCount, int writeGroupStartIndex, bool hasBlobRefs, int blobAssetRefOffsetCount, int blobAssetRefOffsetStartIndex, int weakAssetRefOffsetCount, int weakAssetRefOffsetStartIndex, - int unityObjectRefOffsetCount, int unityObjectRefOffsetStartIndex, int typeSize, ulong bloomFilterMask = 0L) + bool hasUnityObjectRefs, int unityObjectRefOffsetCount, int unityObjectRefOffsetStartIndex, int typeSize, ulong bloomFilterMask = 0L) { TypeIndex = new TypeIndex() { Value = typeIndex }; Category = category; @@ -827,6 +849,7 @@ public TypeInfo(int typeIndex, TypeCategory category, int entityOffsetCount, int WriteGroupCount = writeGroupCount; WriteGroupStartIndex = writeGroupStartIndex; _HasBlobAssetRefs = hasBlobRefs ? 1 : 0; + _HasUnityObjectRefs = hasUnityObjectRefs ? 1 : 0; BlobAssetRefOffsetCount = blobAssetRefOffsetCount; BlobAssetRefOffsetStartIndex = blobAssetRefOffsetStartIndex; WeakAssetRefOffsetCount = weakAssetRefOffsetCount; @@ -903,6 +926,7 @@ public TypeInfo(int typeIndex, TypeCategory category, int entityOffsetCount, int internal readonly int EntityOffsetStartIndex; private readonly int _HasBlobAssetRefs; + private readonly int _HasUnityObjectRefs; /// /// Number of s this component can store. @@ -975,22 +999,26 @@ public int AlignmentInChunkInBytes public bool HasWriteGroups => WriteGroupCount > 0; /// - /// For struct IComponentData, a value of true gurantees that there are fields in this component. + /// For struct IComponentData, a value of true guarantees that there are fields in this component. /// For class based IComponentData, a value of true means it is possible, but not guaranteed, that there are blob asset references. (Polymorphic members can not be proven statically) /// public bool HasBlobAssetRefs => _HasBlobAssetRefs != 0; /// - /// For struct IComponentData, a value of true gurantees that there are fields in this component. + /// For struct IComponentData, a value of true guarantees that there are fields in this component. /// For class based IComponentData, a value of true means it is possible, but not guaranteed, that there are WeakReferences. (Polymorphic members can not be proven statically) /// public bool HasWeakAssetRefs => WeakAssetRefOffsetCount != 0; /// - /// For struct IComponentData, a value of true gurantees that there are fields in this component. - /// For class based IComponentData, a value of true means it is possible, but not guaranteed, that there are WeakReferences. (Polymorphic members can not be proven statically) + /// Returns whether there are any UnityObjectRefs fields in this component. /// - public bool HasUnityObjectRefs => UnityObjectRefOffsetCount != 0; + /// + /// For struct IComponentData a value of `true` means that there are UnityObjectRefs fields in this component. + /// For class based IComponentData, a value of `true` means that there might be UnityObjectRefs fields in this component. This is because polymorphic members can't be proven statically. + /// A value of `false` means there are no UnityObjectRefs fields in this component. + /// + public bool HasUnityObjectRefs => _HasUnityObjectRefs != 0; /// /// Returns the System.Type for the component this is describing. @@ -1590,7 +1618,7 @@ static void RegisterSpecialComponents() new TypeInfo(0, TypeCategory.ComponentData, 0, -1, 0L, 0L, -1, 0, 0, 0, TypeManager.MaximumChunkCapacity, 0, -1, false, 0, - -1, 0, -1, 0, -1, 0, 0L), + -1, 0, -1, false, 0, -1, 0, 0L), "null", 0); // Push Entity TypeInfo @@ -1610,7 +1638,7 @@ static void RegisterSpecialComponents() 0, entityStableTypeHash, -1, UnsafeUtility.SizeOf(), UnsafeUtility.SizeOf(), CalculateAlignmentInChunk(sizeof(Entity)), TypeManager.MaximumChunkCapacity, 0, -1, false, 0, - -1, 0, -1, 0, -1, UnsafeUtility.SizeOf(), + -1, 0, -1, false, 0, -1, UnsafeUtility.SizeOf(), bloomFilterMask:0L), "Unity.Entities.Entity", 0); @@ -2876,14 +2904,14 @@ internal static void CheckIsAllowedAsComponentData(Type type, string baseTypeDes #endif } - internal static void CheckTypeOverrideAndUpdateHasReferences(Type type, Dictionary cache, ref bool hasEntityReferences, ref bool hasBlobReferences) + internal static void CheckTypeOverrideAndUpdateHasReferences(Type type, Dictionary cache, ref bool hasEntityReferences, ref bool hasBlobReferences, ref bool hasUnityObjectReferences) { // Components can opt out of certain patching operations as an optimization since with managed components we can't statically // know if there are certainly entity or blob references. Check here to ensure the type overrides were done correctly. var typeOverride = type.GetCustomAttribute(true); if (typeOverride != null) { - EntityRemapUtility.HasEntityReferencesManaged(type, out var actuallyHasEntityRefs, out var actuallyHasBlobRefs, cache, 128); + EntityRemapUtility.HasEntityReferencesManaged(type, out var actuallyHasEntityRefs, out var actuallyHasBlobRefs, out var actuallyHasUnityObjectRefs, cache, 128); if (typeOverride.HasNoEntityReferences && actuallyHasEntityRefs == EntityRemapUtility.HasRefResult.HasRef) { throw new ArgumentException( @@ -2899,12 +2927,22 @@ internal static void CheckTypeOverrideAndUpdateHasReferences(Type type, Dictiona $"BlobAssetReferences, however the type does in fact contain a (potentially nested) BlobAssetReference. " + $"This is not allowed. Please refer to the documentation for {nameof(TypeOverridesAttribute)} for how to use this attribute."); } + + if (typeOverride.HasNoUnityObjectReferences && !typeOverride.AllowForceNoReferences && actuallyHasUnityObjectRefs == EntityRemapUtility.HasRefResult.HasRef) + { + throw new ArgumentException( + $"Component type '{type}' has a {nameof(TypeOverridesAttribute)} marking the component as not having " + + $"UnityObjectRef, however the type does in fact contain a (potentially nested) UnityObjectRef. " + + $"This is not allowed. Please refer to the documentation for {nameof(TypeOverridesAttribute)} for how to use this attribute."); + } } if (typeOverride != null && typeOverride.HasNoEntityReferences) hasEntityReferences = false; if (typeOverride != null && typeOverride.HasNoBlobReferences) hasBlobReferences = false; + if (typeOverride != null && typeOverride.HasNoUnityObjectReferences) + hasUnityObjectReferences = false; } #if !UNITY_DISABLE_MANAGED_COMPONENTS @@ -3126,6 +3164,7 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, bool hasEntityReferences = false; bool hasBlobReferences = false; bool hasWeakAssetReferences = false; + bool hasUnityObjectRefs = false; if (typeof(IComponentData).IsAssignableFrom(type) && !isManaged) { @@ -3141,7 +3180,7 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, else sizeInChunk = valueTypeSize; - EntityRemapUtility.CalculateFieldOffsetsUnmanaged(type, out hasEntityReferences, out hasBlobReferences, out hasWeakAssetReferences, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, ref s_WeakAssetRefOffsetList, ref s_UnityObjectRefOffsetList, caches.CalculateFieldOffsetsUnmanagedCache); + EntityRemapUtility.CalculateFieldOffsetsUnmanaged(type, out hasEntityReferences, out hasBlobReferences, out hasWeakAssetReferences, out hasUnityObjectRefs, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, ref s_WeakAssetRefOffsetList, ref s_UnityObjectRefOffsetList, caches.CalculateFieldOffsetsUnmanagedCache); } #if !UNITY_DISABLE_MANAGED_COMPONENTS else if (typeof(IComponentData).IsAssignableFrom(type) && isManaged) @@ -3150,10 +3189,11 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, category = TypeCategory.ComponentData; sizeInChunk = sizeof(int); - EntityRemapUtility.HasEntityReferencesManaged(type, out var entityRefResult, out var blobRefResult, caches.HasEntityOrBlobAssetReferenceCache); + EntityRemapUtility.HasEntityReferencesManaged(type, out var entityRefResult, out var blobRefResult, out var unityObjRefResult, caches.HasEntityOrBlobAssetReferenceCache); hasEntityReferences = entityRefResult > 0; hasBlobReferences = blobRefResult > 0; + hasUnityObjectRefs = unityObjRefResult > 0; } #endif else if (typeof(IBufferElementData).IsAssignableFrom(type)) @@ -3168,11 +3208,6 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, elementSize = valueTypeSize; - // Empty types will always be 1 bytes in size as per language requirements - // Check for size 1 first so we don't lookup type fields for all buffer types as it's uncommon - if (elementSize == 1 && type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Length == 0) - throw new ArgumentException($"Type {type} is an IBufferElementData type, however it has no fields and is thus invalid; this will waste chunk space for no benefit. If you want an empty component, consider using IComponentData instead."); - var capacityAttribute = (InternalBufferCapacityAttribute)type.GetCustomAttribute(typeof(InternalBufferCapacityAttribute)); if (capacityAttribute != null) bufferCapacity = capacityAttribute.Capacity; @@ -3180,7 +3215,7 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, bufferCapacity = DefaultBufferCapacityNumerator / elementSize; // Rather than 2*cachelinesize, to make it cross platform deterministic sizeInChunk = sizeof(BufferHeader) + bufferCapacity * elementSize; - EntityRemapUtility.CalculateFieldOffsetsUnmanaged(type, out hasEntityReferences, out hasBlobReferences, out hasWeakAssetReferences, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, ref s_WeakAssetRefOffsetList, ref s_UnityObjectRefOffsetList, caches.CalculateFieldOffsetsUnmanagedCache); + EntityRemapUtility.CalculateFieldOffsetsUnmanaged(type, out hasEntityReferences, out hasBlobReferences, out hasWeakAssetReferences, out hasUnityObjectRefs, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, ref s_WeakAssetRefOffsetList, ref s_UnityObjectRefOffsetList, caches.CalculateFieldOffsetsUnmanagedCache); } else if (typeof(ISharedComponentData).IsAssignableFrom(type)) { @@ -3191,25 +3226,21 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, #endif valueTypeSize = UnsafeUtility.SizeOf(type); - // Empty types will always be 1 bytes in size as per language requirements - // Check for size 1 first so we don't lookup type fields for all buffer types as it's uncommon - if (valueTypeSize == 1 && type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Length == 0) - throw new ArgumentException($"Type {type} is an ISharedComponentData type, however it has no fields and is thus invalid; this will waste chunk space for no benefit. If you want an empty component, consider using IComponentData instead."); - category = TypeCategory.ISharedComponentData; isManaged = !UnsafeUtility.IsUnmanaged(type); if (isManaged) { - EntityRemapUtility.HasEntityReferencesManaged(type, out var entityRefResult, out var blobRefResult, caches.HasEntityOrBlobAssetReferenceCache); + EntityRemapUtility.HasEntityReferencesManaged(type, out var entityRefResult, out var blobRefResult, out var unityObjectRefResult, caches.HasEntityOrBlobAssetReferenceCache); // Managed shared components explicitly do not allow patching of entity references hasEntityReferences = false; hasBlobReferences = blobRefResult > 0; + hasUnityObjectRefs = unityObjectRefResult > 0; } else { - EntityRemapUtility.CalculateFieldOffsetsUnmanaged(type, out hasEntityReferences, out hasBlobReferences, out hasWeakAssetReferences, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, ref s_WeakAssetRefOffsetList, ref s_UnityObjectRefOffsetList, caches.CalculateFieldOffsetsUnmanagedCache); + EntityRemapUtility.CalculateFieldOffsetsUnmanaged(type, out hasEntityReferences, out hasBlobReferences, out hasWeakAssetReferences, out hasUnityObjectRefs, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, ref s_WeakAssetRefOffsetList, ref s_UnityObjectRefOffsetList, caches.CalculateFieldOffsetsUnmanagedCache); } } else if (type.IsClass) @@ -3219,6 +3250,7 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, alignmentInBytes = sizeof(int); hasEntityReferences = false; hasBlobReferences = false; + hasUnityObjectRefs = false; #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG if (UnityEngineObjectType == null) @@ -3234,7 +3266,7 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, } // If the type explicitly overrides entity/blob reference attributes account for that now - CheckTypeOverrideAndUpdateHasReferences(type, caches.HasEntityOrBlobAssetReferenceCache, ref hasEntityReferences, ref hasBlobReferences); + CheckTypeOverrideAndUpdateHasReferences(type, caches.HasEntityOrBlobAssetReferenceCache, ref hasEntityReferences, ref hasBlobReferences, ref hasUnityObjectRefs); AddFastEqualityInfo(type, category == TypeCategory.UnityEngineObject, caches.FastEqualityLayoutInfoCache); @@ -3319,7 +3351,7 @@ internal static TypeInfo BuildComponentType(Type type, TypeIndex[] writeGroups, elementSize > 0 ? elementSize : sizeInChunk, alignmentInBytes, maxChunkCapacity, writeGroupCount, writeGroupIndex, hasBlobReferences, blobAssetRefOffsetCount, blobAssetRefOffsetIndex, - weakAssetRefOffsetCount, weakAssetRefOffsetIndex, unityObjectRefOffsetCount, + weakAssetRefOffsetCount, weakAssetRefOffsetIndex, hasUnityObjectRefs, unityObjectRefOffsetCount, unityObjectRefOffsetIndex, valueTypeSize, bloomFilterMask); } diff --git a/Unity.Entities/Unity.Entities.asmdef b/Unity.Entities/Unity.Entities.asmdef index 1186ca37..4dc15bcc 100644 --- a/Unity.Entities/Unity.Entities.asmdef +++ b/Unity.Entities/Unity.Entities.asmdef @@ -33,7 +33,17 @@ "name": "Unity", "expression": "2022.3.11f1", "define": "UNITY_2022_3_11F1_OR_NEWER" + }, + { + "name": "Unity", + "expression": "2022.3.43f1", + "define": "UNITY_2022_3_43F1_OR_NEWER" + }, + { + "name": "Unity", + "expression": "6000.0.16f1", + "define": "UNITY_6000_0_16F1_OR_NEWER" } ], "noEngineReferences": false -} +} \ No newline at end of file diff --git a/ValidationExceptions.json b/ValidationExceptions.json index 425d80ce..0125603d 100644 --- a/ValidationExceptions.json +++ b/ValidationExceptions.json @@ -3,7 +3,22 @@ { "ValidationTest": "API Validation", "ExceptionMessage": "Breaking changes require a new major version.", - "PackageVersion": "1.3.0-pre.4" + "PackageVersion": "1.3.2" + }, + { + "ValidationTest": "API Validation", + "ExceptionMessage": "Additions require a new minor or major version.", + "PackageVersion": "1.3.2" + }, + { + "ValidationTest": "API Validation", + "ExceptionMessage": "New assembly \"Unity.Entities.TestComponents\" may only be added in a new minor or major version.", + "PackageVersion": "1.3.2" + }, + { + "ValidationTest": "API Validation", + "ExceptionMessage": "New assembly \"Unity.Scenes.PerformanceTests\" may only be added in a new minor or major version.", + "PackageVersion": "1.3.2" } ], "WarningExceptions": [] diff --git a/package.json b/package.json index ca0e3e52..03f5f8d3 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "com.unity.entities", "displayName": "Entities", - "version": "1.3.0-pre.4", + "version": "1.3.2", "unity": "2022.3", "unityRelease": "11f1", "dependencies": { - "com.unity.burst": "1.8.16", + "com.unity.burst": "1.8.17", "com.unity.serialization": "3.1.1", - "com.unity.collections": "2.5.0-pre.2", + "com.unity.collections": "2.5.1", "com.unity.mathematics": "1.3.1", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.audio": "1.0.0", @@ -16,7 +16,9 @@ "com.unity.test-framework.performance": "3.0.3", "com.unity.nuget.mono-cecil": "1.11.4", "com.unity.scriptablebuildpipeline": "1.21.21", - "com.unity.profiling.core": "1.0.2" + "com.unity.profiling.core": "1.0.2", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.uielements": "1.0.0" }, "description": "The Entities package provides a modern Entity Component System (ECS) implementation with a basic set of systems and components made for Unity.", "keywords": [ @@ -25,15 +27,15 @@ "unity" ], "_upm": { - "changelog": "### Changed\n\n* Updated Burst dependency to version 1.8.16\n\n### Deprecated\n\n* The `ENABLE_SIMPLE_SYSTEM_DEPENDENCIES` feature will be removed in a future package release, as it no longer provides significant benefit for expected DOTS workloads.\n* The `EntityQueryCaptureMode.AtRecord` enum value in `EntityCommandBuffer` is now deprecated. All users should migrate to `EntityQueryCaptureMode.AtPlayback`. Capture-at-record mode can be several hundred times slower than capture-at-playback. If capture-at-record semantics are required and performance isn't a concern, the array of entities matching the query can be captured manually and passed to the corresponding `EntityCommandBuffer` command.\n\n### Fixed\n\n* Usage of SystemAPI.GetComponentRW and SystemAPI.GetComponentRO in Entities.ForEach.\n* Regression in compilation time with assemblies with lots of system methods.\n* EntityComponentStore leaked memory during domain reload." + "changelog": "### Added\n\n* Overloads for `ComponentLookup.HasComponent`, `ComponentLookup.TryGetComponent`, `BufferLookup.HasBuffer`, and `BufferLookup.TryGetBuffer` adding parameter `out bool entityExists`, as well as dedicated `ComponentLookup.EntityExists` and `BufferLookup.EntityExists` APIs, to allow user-code to distinguish entity non-existence from component non-existence without additional boilerplate, inside jobs.\n* adding missing dependencies `com.unity.modules.physics`, `com.unity.modules.uielements`.\n\n### Changed\n\n* Updated Burst dependency to version 1.8.17\n* Add API docs discouraging the use of the `ExclusiveEntityTransaction.EntityManager` property. Many EntityManager operations are not safe to use within the context of an ExclusiveEntityTransaction; only the methods directly exposed by `ExclusiveEntityTransaction` are guaranteed to work correctly.\n* Add API docs discouraging the creation of `EntityQuery` objects with multiple query descriptions. This feature works in narrow contexts, but is known to cause errors and incompatibilities with other DOTS features.\n* Add API docs to note that enabling and disabling components inside `IJobEntityChunkBeginEnd.OnChunkBegin()` does not affect the entities iterated in the current chunk, as its `chunkEnabledMask` has already been computed.\n* Zero-size `IBufferElementData` and `ISharedComponentData` structs no longer cause the TypeManager to throw during initialization. Zero-size buffer and shared components are usually a sign of pointless waste (especially buffer components, which have a significant chunk-memory cost even if the actual elements are empty), but they shouldn't cause a fatal error.\n\n### Fixed\n\n* Various SGICE002 errors that happen if you type invalid C# code\n* Various SGICE003 errors that happen if you type invalid C# code\n* NullReferenceException on UnityObjectRef after Asset Garbage Collection (This fix requires editor versions 2022.3.43f1 and 6000.0.16f1 and beyond)" }, "upmCi": { - "footprint": "ad441cc1d5a05669016d42fc54556c8672e3000d" + "footprint": "5ee7373e0991ffdf47a7a47d2d5ea184ea08d0df" }, "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/index.html", "repository": { "url": "https://github.cds.internal.unity3d.com/unity/dots.git", "type": "git", - "revision": "6b0c8d27a0224c9cf76032c48d1b981e9750f10d" + "revision": "921920e681054c59b440cc1e2aef10f781dc4124" } }