diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4c2154cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +Unity Companion License (“License”) +Software Copyright © 2017 Unity Technologies ApS + +Unity Technologies ApS (“Unity”) grants to you a worldwide, non-exclusive, no-charge, and royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute the software that is made available under this License (“Software”), subject to the following terms and conditions: + +1. Unity Companion Use Only. Exercise of the license granted herein is limited to exercise for the creation, use, and/or distribution of applications, software, or other content pursuant to a valid Unity content authoring and rendering engine software license (“Engine License”). That means while use of the Software is not limited to use in the software licensed under the Engine License, the Software may not be used for any purpose other than the creation, use, and/or distribution of Engine License-dependent applications, software, or other content. No other exercise of the license granted herein is permitted, and in no event may the Software be used for competitive analysis or to develop a competing product or service. + +2. No Modification of Engine License. Neither this License nor any exercise of the license granted herein modifies the Engine License in any way. + +3. Ownership & Grant Back to You. + +3.1 You own your content. In this License, “derivative works” means derivatives of the Software itself--works derived only from the Software by you under this License (for example, modifying the code of the Software itself to improve its efficacy); “derivative works” of the Software do not include, for example, games, apps, or content that you create using the Software. You keep all right, title, and interest to your own content. + +3.2 Unity owns its content. While you keep all right, title, and interest to your own content per the above, as between Unity and you, Unity will own all right, title, and interest to all intellectual property rights (including patent, trademark, and copyright) in the Software and derivative works of the Software, and you hereby assign and agree to assign all such rights in those derivative works to Unity. + +3.3 You have a license to those derivative works. Subject to this License, Unity grants to you the same worldwide, non-exclusive, no-charge, and royalty-free copyright license to derivative works of the Software you create as is granted to you for the Software under this License. + +4. Trademarks. You are not granted any right or license under this License to use any trademarks, service marks, trade names, products names, or branding of Unity or its affiliates (“Trademarks”). Descriptive uses of Trademarks are permitted; see, for example, Unity’s Branding Usage Guidelines at https://unity3d.com/public-relations/brand. + +5. Notices & Third-Party Rights. This License, including the copyright notice associated with the Software, must be provided in all substantial portions of the Software and derivative works thereof (or, if that is impracticable, in any other location where such notices are customarily placed). Further, if the Software is accompanied by a Unity “third-party notices” or similar file, you acknowledge and agree that software identified in that file is governed by those separate license terms. + +6. DISCLAIMER, LIMITATION OF LIABILITY. THE SOFTWARE AND ANY DERIVATIVE WORKS THEREOF IS PROVIDED ON AN "AS IS" BASIS, AND IS PROVIDED WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND/OR NONINFRINGEMENT. IN NO EVENT SHALL ANY COPYRIGHT HOLDER OR AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES (WHETHER DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL, INCLUDING PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF USE, DATA, OR PROFITS, AND BUSINESS INTERRUPTION), OR OTHER LIABILITY WHATSOEVER, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM OR OUT OF, OR IN CONNECTION WITH, THE SOFTWARE OR ANY DERIVATIVE WORKS THEREOF OR THE USE OF OR OTHER DEALINGS IN SAME, EVEN WHERE ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +7. USE IS ACCEPTANCE and License Versions. Your receipt and use of the Software constitutes your acceptance of this License and its terms and conditions. Software released by Unity under this License may be modified or updated and the License with it; upon any such modification or update, you will comply with the terms of the updated License for any use of any of the Software under the updated License. + +8. Use in Compliance with Law and Termination. Your exercise of the license granted herein will at all times be in compliance with applicable law and will not infringe any proprietary rights (including intellectual property rights); this License will terminate immediately on any breach by you of this License. + +9. Severability. If any provision of this License is held to be unenforceable or invalid, that provision will be enforced to the maximum extent possible and the other provisions will remain in full force and effect. + +10. Governing Law and Venue. This License is governed by and construed in accordance with the laws of Denmark, except for its conflict of laws rules; the United Nations Convention on Contracts for the International Sale of Goods will not apply. If you reside (or your principal place of business is) within the United States, you and Unity agree to submit to the personal and exclusive jurisdiction of and venue in the state and federal courts located in San Francisco County, California concerning any dispute arising out of this License (“Dispute”). If you reside (or your principal place of business is) outside the United States, you and Unity agree to submit to the personal and exclusive jurisdiction of and venue in the courts located in Copenhagen, Denmark concerning any Dispute. \ No newline at end of file diff --git a/LICENSE.meta b/LICENSE.meta new file mode 100644 index 00000000..ea7decbc --- /dev/null +++ b/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 559b8f3d8bc388f4584b463e878e6576 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor.Tests.meta b/Unity.Entities.Editor.Tests.meta new file mode 100644 index 00000000..3c2c2213 --- /dev/null +++ b/Unity.Entities.Editor.Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2da84dd03325e49b99e575e89a0cb61d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor.Tests/ComponentTypeFilterUITests.cs b/Unity.Entities.Editor.Tests/ComponentTypeFilterUITests.cs new file mode 100644 index 00000000..cbb04588 --- /dev/null +++ b/Unity.Entities.Editor.Tests/ComponentTypeFilterUITests.cs @@ -0,0 +1,49 @@ +using System; +using NUnit.Framework; +using Unity.Entities.Tests; +using UnityEditor; +using UnityEngine; + +namespace Unity.Entities.Editor.Tests +{ + public class ComponentTypeFilterUITests : ECSTestsFixture + { + + public void SetFilterDummy(ComponentGroup group) + { + + } + + private World WorldSelectionGetter() + { + return World.Active; + } + + [Test] + public void ComponentTypeFilterUI_GetTypesIgnoresNullWorld() + { + var filterUI = new ComponentTypeFilterUI(SetFilterDummy, () => null); + Assert.DoesNotThrow(filterUI.GetTypes); + } + + [Test] + public void ComponentTypeFilterUI_ComparisonToTypeManagerCorrect() + { + var filterUI = new ComponentTypeFilterUI(SetFilterDummy, WorldSelectionGetter); + Assert.IsFalse(filterUI.TypeListValid()); + filterUI.GetTypes(); + Assert.IsTrue(filterUI.TypeListValid()); + } + + [Test] + public void ComponentTypeFilterUI_ComponentGroupCaches() + { + var filterUI = new ComponentTypeFilterUI(SetFilterDummy, WorldSelectionGetter); + var types = new ComponentType[] + {ComponentType.Create(), ComponentType.ReadOnly()}; + Assert.IsNull(filterUI.GetExistingGroup(types)); + var group = filterUI.GetComponentGroup(types); + Assert.AreEqual(group, filterUI.GetExistingGroup(types)); + } + } +} diff --git a/Unity.Entities.Editor.Tests/ComponentTypeFilterUITests.cs.meta b/Unity.Entities.Editor.Tests/ComponentTypeFilterUITests.cs.meta new file mode 100644 index 00000000..7b44bae6 --- /dev/null +++ b/Unity.Entities.Editor.Tests/ComponentTypeFilterUITests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d1802275260543cb944ae36805bf086 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor.Tests/EntityDebuggerTests.cs b/Unity.Entities.Editor.Tests/EntityDebuggerTests.cs new file mode 100644 index 00000000..033700a8 --- /dev/null +++ b/Unity.Entities.Editor.Tests/EntityDebuggerTests.cs @@ -0,0 +1,129 @@ +using System; +using NUnit.Framework; +using Unity.Entities.Tests; +using UnityEditor; +using UnityEngine; + +namespace Unity.Entities.Editor.Tests +{ + public class EntityDebuggerTests : ECSTestsFixture + { + + private EntityDebugger m_Window; + private ComponentSystem m_System; + private ComponentGroup m_ComponentGroup; + private Entity m_Entity; + + class SingleGroupSystem : ComponentSystem + { + +#pragma warning disable 0169 // "never used" warning + struct Group + { + private int Length; + private ComponentDataArray testDatas; + } + + [Inject] private Group entities; +#pragma warning restore 0169 + + protected override void OnUpdate() + { + throw new NotImplementedException(); + } + } + + private static void CloseAllDebuggers() + { + var windows = Resources.FindObjectsOfTypeAll(); + foreach (var window in windows) + window.Close(); + } + + public override void Setup() + { + base.Setup(); + + CloseAllDebuggers(); + + m_Window = EditorWindow.GetWindow(); + + m_System = World.Active.GetOrCreateManager(); + + m_ComponentGroup = m_System.ComponentGroups[0]; + + m_Entity = m_Manager.CreateEntity(typeof(EcsTestData)); + } + + public override void TearDown() + { + CloseAllDebuggers(); + + base.TearDown(); + } + + [Test] + public void EntityDebugger_SetAllSelections() + { + + EntityDebugger.SetAllSelections(World.Active, m_System, m_ComponentGroup, m_Entity); + + Assert.AreEqual(World.Active, m_Window.WorldSelection); + Assert.AreEqual(m_System, m_Window.SystemSelection); + Assert.AreEqual(m_ComponentGroup, m_Window.ComponentGroupSelection); + Assert.AreEqual(m_Entity, m_Window.EntitySelection); + } + + [Test] + public void EntityDebugger_RememberSelections() + { + + EntityDebugger.SetAllSelections(World.Active, m_System, m_ComponentGroup, m_Entity); + + m_Window.SetWorldSelection(null, true); + + m_Window.SetWorldSelection(World.Active, true); + + Assert.AreEqual(World.Active, m_Window.WorldSelection); + Assert.AreEqual(m_System, m_Window.SystemSelection); + Assert.AreEqual(m_ComponentGroup, m_Window.ComponentGroupSelection); + Assert.AreEqual(m_Entity, m_Window.EntitySelection); + } + + [Test] + public void EntityDebugger_SetAllEntitiesFilter() + { + var components = new ComponentType[] {ComponentType.Create() }; + var componentGroup = World.Active.GetExistingManager().CreateComponentGroup(components); + + m_Window.SetWorldSelection(World.Active, true); + m_Window.SetSystemSelection(null, true, true); + m_Window.SetAllEntitiesFilter(componentGroup); + Assert.AreEqual(componentGroup, m_Window.ComponentGroupSelection); + + m_Window.SetComponentGroupSelection(null, true, true); + m_Window.SetSystemSelection(World.Active.GetExistingManager(), true, true); + m_Window.SetAllEntitiesFilter(componentGroup); + Assert.AreEqual(componentGroup, m_Window.ComponentGroupSelection); + + m_Window.SetSystemSelection(m_System, true, true); + m_Window.SetAllEntitiesFilter(componentGroup); + Assert.AreNotEqual(componentGroup, m_Window.ComponentGroupSelection); + } + + [Test] + public void EntityDebugger_StylesIntact() + { + Assert.IsNotNull(EntityDebuggerStyles.ComponentRequired); + Assert.IsNotNull(EntityDebuggerStyles.ComponentSubtractive); + Assert.IsNotNull(EntityDebuggerStyles.ComponentReadOnly); + Assert.IsNotNull(EntityDebuggerStyles.ComponentReadWrite); + + Assert.IsNotNull(EntityDebuggerStyles.ComponentRequired.normal.background); + Assert.IsNotNull(EntityDebuggerStyles.ComponentSubtractive.normal.background); + Assert.IsNotNull(EntityDebuggerStyles.ComponentReadOnly.normal.background); + Assert.IsNotNull(EntityDebuggerStyles.ComponentReadWrite.normal.background); + } + + } +} diff --git a/Unity.Entities.Editor.Tests/EntityDebuggerTests.cs.meta b/Unity.Entities.Editor.Tests/EntityDebuggerTests.cs.meta new file mode 100644 index 00000000..7f3b7e0c --- /dev/null +++ b/Unity.Entities.Editor.Tests/EntityDebuggerTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5db40a1516f304b528d9d84f0bcb29b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor.Tests/ListViewTests.cs b/Unity.Entities.Editor.Tests/ListViewTests.cs new file mode 100644 index 00000000..f798f804 --- /dev/null +++ b/Unity.Entities.Editor.Tests/ListViewTests.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Entities.Tests; +using UnityEditor.IMGUI.Controls; + +namespace Unity.Entities.Editor.Tests +{ + public class ListViewTests : ECSTestsFixture + { + public static void SetEntitySelection(Entity s, bool updateList) + { + } + + public World GetWorldSelection() + { + return World.Active; + } + + public static void SetComponentGroupSelection(ComponentGroup group, bool updateList, bool propagate) + { + } + + public static void SetSystemSelection(ScriptBehaviourManager system, bool updateList, bool propagate) + { + } + + [Test] + public void EntityListView_CanSetNullGroup() + { + var listView = new EntityListView(new TreeViewState(), null, SetEntitySelection, GetWorldSelection); + + Assert.DoesNotThrow( () => listView.SelectedComponentGroup = null ); + } + + [Test] + public void ComponentGroupListView_CanSetNullSystem() + { + var listView = new ComponentGroupListView(new TreeViewState(), EmptySystem, SetComponentGroupSelection, GetWorldSelection); + + Assert.DoesNotThrow(() => listView.SelectedSystem = null); + } + + [Test] + public void SystemListView_CanCreateWithNullWorld() + { + SystemListView listView; + var states = new List(); + var stateNames = new List(); + Assert.DoesNotThrow(() => + { + listView = SystemListView.CreateList(states, stateNames, SetSystemSelection, GetWorldSelection); + listView.Reload(); + }); + } + + [Test] + public void ComponentGroupListView_SortOrderExpected() + { + var typeList = new List(); + var subtractive = ComponentType.Subtractive(); + var readWrite = ComponentType.Create(); + var readOnly = ComponentType.ReadOnly(); + + typeList.Add(subtractive); + typeList.Add(readOnly); + typeList.Add(readWrite); + typeList.Sort(ComponentGroupListView.CompareTypes); + + Assert.AreEqual(readOnly, typeList[0]); + Assert.AreEqual(readWrite, typeList[1]); + Assert.AreEqual(subtractive, typeList[2]); + } + + } +} diff --git a/Unity.Entities.Editor.Tests/ListViewTests.cs.meta b/Unity.Entities.Editor.Tests/ListViewTests.cs.meta new file mode 100644 index 00000000..8690d812 --- /dev/null +++ b/Unity.Entities.Editor.Tests/ListViewTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c052179a9eeb4183a37a99d1ecaa056 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor.Tests/Unity.Entities.Editor.Tests.asmdef b/Unity.Entities.Editor.Tests/Unity.Entities.Editor.Tests.asmdef new file mode 100644 index 00000000..a51843b5 --- /dev/null +++ b/Unity.Entities.Editor.Tests/Unity.Entities.Editor.Tests.asmdef @@ -0,0 +1,17 @@ +{ + "name": "Unity.Entities.Editor.Tests", + "references": [ + "Unity.Entities", + "Unity.Entities.Editor", + "Unity.Collections", + "Unity.Entities.Tests" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true +} diff --git a/Unity.Entities.Editor.Tests/Unity.Entities.Editor.Tests.asmdef.meta b/Unity.Entities.Editor.Tests/Unity.Entities.Editor.Tests.asmdef.meta new file mode 100644 index 00000000..b74b343e --- /dev/null +++ b/Unity.Entities.Editor.Tests/Unity.Entities.Editor.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8fe01134beaec4b108c4c1a154e71dc2 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor.meta b/Unity.Entities.Editor.meta new file mode 100644 index 00000000..4e147571 --- /dev/null +++ b/Unity.Entities.Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c9c0d268c3171433e946229b2097a361 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/AssemblyInfo.cs b/Unity.Entities.Editor/AssemblyInfo.cs new file mode 100644 index 00000000..97a7d283 --- /dev/null +++ b/Unity.Entities.Editor/AssemblyInfo.cs @@ -0,0 +1,4 @@ + +using System.Runtime.CompilerServices; + +[assembly:InternalsVisibleTo("Unity.Entities.Editor.Tests")] diff --git a/Unity.Entities.Editor/AssemblyInfo.cs.meta b/Unity.Entities.Editor/AssemblyInfo.cs.meta new file mode 100644 index 00000000..7e2def3b --- /dev/null +++ b/Unity.Entities.Editor/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e69a71452f34e41de8f9a1147c919b9d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger.meta b/Unity.Entities.Editor/EntityDebugger.meta new file mode 100644 index 00000000..9c4eb857 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4e2b0c054b566436d9af471e5ab1edf2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/ComponentGroupListView.cs b/Unity.Entities.Editor/EntityDebugger/ComponentGroupListView.cs new file mode 100644 index 00000000..9cfee06f --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/ComponentGroupListView.cs @@ -0,0 +1,224 @@ +using System; +using UnityEditor.IMGUI.Controls; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + + public delegate void SetComponentGroupSelection(ComponentGroup group, bool updateList, bool propagate); + + public class ComponentGroupListView : TreeView { + private readonly Dictionary componentGroupsById = new Dictionary(); + private readonly Dictionary componentGroupTypeNamesById = new Dictionary(); + private readonly Dictionary componentGroupTypeModesById = new Dictionary(); + + public ComponentSystemBase SelectedSystem + { + get { return selectedSystem; } + set + { + if (selectedSystem != value) + { + selectedSystem = value; + Reload(); + } + } + } + private ComponentSystemBase selectedSystem; + + private readonly WorldSelectionGetter getWorldSelection; + private readonly SetComponentGroupSelection componentGroupSelectionCallback; + + private static TreeViewState GetStateForSystem(ComponentSystemBase system, List states, List stateNames) + { + if (system == null) + return new TreeViewState(); + + var currentSystemName = system.GetType().FullName; + + var stateForCurrentSystem = states.Where((t, i) => stateNames[i] == currentSystemName).FirstOrDefault(); + if (stateForCurrentSystem != null) + return stateForCurrentSystem; + + stateForCurrentSystem = new TreeViewState(); + if (system.ComponentGroups != null && system.ComponentGroups.Length > 0) + stateForCurrentSystem.expandedIDs = new List {1}; + states.Add(stateForCurrentSystem); + stateNames.Add(currentSystemName); + return stateForCurrentSystem; + } + + public static ComponentGroupListView CreateList(ComponentSystemBase system, List states, List stateNames, + SetComponentGroupSelection componentGroupSelectionCallback, WorldSelectionGetter worldSelectionGetter) + { + var state = GetStateForSystem(system, states, stateNames); + return new ComponentGroupListView(state, system, componentGroupSelectionCallback, worldSelectionGetter); + } + + public ComponentGroupListView(TreeViewState state, ComponentSystemBase system, SetComponentGroupSelection componentGroupSelectionCallback, WorldSelectionGetter worldSelectionGetter) : base(state) + { + this.getWorldSelection = worldSelectionGetter; + this.componentGroupSelectionCallback = componentGroupSelectionCallback; + selectedSystem = system; + rowHeight += 1; + Reload(); + } + + public float Height => Mathf.Max(selectedSystem?.ComponentGroups?.Length ?? 0, 1)*rowHeight; + + internal static int CompareTypes(ComponentType x, ComponentType y) + { + var accessModeOrder = SortOrderFromAccessMode(x.AccessModeType).CompareTo(SortOrderFromAccessMode(y.AccessModeType)); + return accessModeOrder != 0 ? accessModeOrder : string.Compare(x.GetType().Name, y.GetType().Name, StringComparison.InvariantCulture); + } + + private static int SortOrderFromAccessMode(ComponentType.AccessMode mode) + { + switch (mode) + { + case ComponentType.AccessMode.ReadOnly: + return 0; + case ComponentType.AccessMode.ReadWrite: + return 1; + case ComponentType.AccessMode.Subtractive: + return 2; + default: + throw new ArgumentException("Unrecognized AccessMode"); + } + } + + protected override TreeViewItem BuildRoot() + { + componentGroupsById.Clear(); + componentGroupTypeModesById.Clear(); + componentGroupTypeNamesById.Clear(); + var currentId = 0; + var root = new TreeViewItem { id = currentId++, depth = -1, displayName = "Root" }; + if (getWorldSelection() == null) + { + root.AddChild(new TreeViewItem { id = currentId, displayName = "No world selected"}); + } + else if (SelectedSystem == null) + { + root.AddChild(new TreeViewItem { id = currentId, displayName = "Null System"}); + } + else if (SelectedSystem.ComponentGroups == null || SelectedSystem.ComponentGroups.Length == 0) + { + root.AddChild(new TreeViewItem { id = currentId, displayName = "No Component Groups in Manager"}); + } + else + { + foreach (var group in SelectedSystem.ComponentGroups) + { + componentGroupsById.Add(currentId, group); + var types = new List(group.Types.Skip(1)); // Skip Entity + types.Sort(CompareTypes); + componentGroupTypeNamesById.Add(currentId, (from t in types select t.GetManagedType().Name).ToArray()); + componentGroupTypeModesById.Add(currentId, (from t in types select t.AccessModeType).ToArray()); + + var groupItem = new TreeViewItem { id = currentId++ }; + root.AddChild(groupItem); + } + SetupDepthsFromParentsAndChildren(root); + } + return root; + } + + public override void OnGUI(Rect rect) + { + if (getWorldSelection()?.GetExistingManager()?.IsCreated == true) + base.OnGUI(rect); + } + + private void DrawTypeAndMovePosition(ref Vector2 position, string name, ComponentType.AccessMode mode) + { + var style = StyleForAccessMode(mode); + var content = new GUIContent(name); + var labelRect = new Rect(position, style.CalcSize(content)); + GUI.Label(labelRect, content, style); + position.x += labelRect.width + 2f; + } + + internal static GUIStyle StyleForAccessMode(ComponentType.AccessMode mode) + { + switch (mode) + { + case ComponentType.AccessMode.ReadOnly: + return EntityDebuggerStyles.ComponentReadOnly; + case ComponentType.AccessMode.ReadWrite: + return EntityDebuggerStyles.ComponentReadWrite; + case ComponentType.AccessMode.Subtractive: + return EntityDebuggerStyles.ComponentSubtractive; + default: + throw new ArgumentException("Unrecognized access mode"); + } + } + + protected override void RowGUI(RowGUIArgs args) + { + base.RowGUI(args); + if (!componentGroupsById.ContainsKey(args.item.id)) + return; + + var componentGroup = componentGroupsById[args.item.id]; + var names = componentGroupTypeNamesById[args.item.id]; + var modes = componentGroupTypeModesById[args.item.id]; + + var position = args.rowRect.position; + position.x = GetContentIndent(args.item); + position.y += 1; + + for (var i = 0; i < names.Length; ++i) + { + DrawTypeAndMovePosition(ref position, names[i], modes[i]); + } + + var countString = componentGroup.CalculateLength().ToString(); + DefaultGUI.LabelRightAligned(args.rowRect, countString, args.selected, args.focused); + } + + protected override void SelectionChanged(IList selectedIds) + { + if (selectedIds.Count > 0 && componentGroupsById.ContainsKey(selectedIds[0])) + { + componentGroupSelectionCallback(componentGroupsById[selectedIds[0]], false, true); + } + else + { + componentGroupSelectionCallback(null, false, true); + } + } + + protected override bool CanMultiSelect(TreeViewItem item) + { + return false; + } + + public void SetComponentGroupSelection(ComponentGroup group) + { + foreach (var pair in componentGroupsById) + { + if (pair.Value == group) + { + SetSelection(new List {pair.Key}); + return; + } + } + SetSelection(new List()); + } + + public void TouchSelection() + { + SetSelection(GetSelection(), TreeViewSelectionOptions.FireSelectionChanged); + } + + public void UpdateIfNecessary() + { + var expectedGroupCount = SelectedSystem?.ComponentGroups?.Length ?? 0; + if (expectedGroupCount != componentGroupsById.Count) + Reload(); + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/ComponentGroupListView.cs.meta b/Unity.Entities.Editor/EntityDebugger/ComponentGroupListView.cs.meta new file mode 100644 index 00000000..80c2124a --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/ComponentGroupListView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e52e54b28c334b7191e628a17ac12af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/ComponentTypeChooser.cs b/Unity.Entities.Editor/EntityDebugger/ComponentTypeChooser.cs new file mode 100644 index 00000000..eb41dfe4 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/ComponentTypeChooser.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + + public delegate void CallbackAction(); + + public class ComponentTypeChooser : EditorWindow + { + + private static List types; + private static List typeSelections; + + private static CallbackAction callback; + + private static readonly Vector2 kDefaultSize = new Vector2(300f, 400f); + + public static void Open(Vector2 screenPosition, List types, List typeSelections, CallbackAction callback) + { + ComponentTypeChooser.callback = callback; + ComponentTypeChooser.types = types; + ComponentTypeChooser.typeSelections = typeSelections; + GetWindowWithRect(new Rect(screenPosition, kDefaultSize), true, "Choose Component", true); + } + + private SearchField searchField; + private ComponentTypeListView typeListView; + + private void OnEnable() + { + searchField = new SearchField(); + searchField.SetFocus(); + typeListView = new ComponentTypeListView(new TreeViewState(), types, typeSelections, ComponentFilterChanged); + } + + public void ComponentFilterChanged() + { + callback(); + } + + private void OnGUI() + { + typeListView.searchString = searchField.OnGUI(typeListView.searchString, GUILayout.Height(20f), GUILayout.ExpandWidth(true)); + typeListView.OnGUI(GUIHelpers.GetExpandingRect()); + } + + private void OnLostFocus() + { + Close(); + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/ComponentTypeChooser.cs.meta b/Unity.Entities.Editor/EntityDebugger/ComponentTypeChooser.cs.meta new file mode 100644 index 00000000..2c47b455 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/ComponentTypeChooser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8598b328594b4e7d888a2a2546845fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/ComponentTypeFilterUI.cs b/Unity.Entities.Editor/EntityDebugger/ComponentTypeFilterUI.cs new file mode 100644 index 00000000..9b0f0ed8 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/ComponentTypeFilterUI.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + public delegate void SetFilterAction(ComponentGroup componentGroup); + + public class ComponentTypeFilterUI + { + private readonly WorldSelectionGetter getWorldSelection; + private readonly SetFilterAction setFilter; + + private readonly List selectedFilterTypes = new List(); + private readonly List filterTypes = new List(); + + private readonly List componentGroups = new List(); + + public ComponentTypeFilterUI(SetFilterAction setFilter, WorldSelectionGetter worldSelectionGetter) + { + getWorldSelection = worldSelectionGetter; + this.setFilter = setFilter; + } + + internal bool TypeListValid() + { + return selectedFilterTypes.Count == 2 * (TypeManager.TypesCount - 2); // First two entries are not ComponentTypes + } + + internal void GetTypes() + { + if (getWorldSelection() == null) return; + if (!TypeListValid()) + { + filterTypes.Clear(); + selectedFilterTypes.Clear(); + var requiredTypes = new List(); + var subtractiveTypes = new List(); + filterTypes.Capacity = TypeManager.TypesCount; + selectedFilterTypes.Capacity = TypeManager.TypesCount; + foreach (var type in TypeManager.AllTypes()) + { + if (type.Type == typeof(Entity)) continue; + var typeIndex = TypeManager.GetTypeIndex(type.Type); + var componentType = ComponentType.FromTypeIndex(typeIndex); + if (componentType.GetManagedType() == null) continue; + requiredTypes.Add(componentType); + componentType.AccessModeType = ComponentType.AccessMode.Subtractive; + subtractiveTypes.Add(componentType); + selectedFilterTypes.Add(false); + selectedFilterTypes.Add(false); + } + + filterTypes.AddRange(requiredTypes); + filterTypes.AddRange(subtractiveTypes); + } + } + + public void OnGUI() + { + GUILayout.BeginHorizontal(); + GUILayout.Label("Filter: "); + var filterCount = 0; + for (var i = 0; i < selectedFilterTypes.Count; ++i) + { + if (selectedFilterTypes[i]) + { + ++filterCount; + var style = filterTypes[i].AccessModeType == ComponentType.AccessMode.Subtractive ? EntityDebuggerStyles.ComponentSubtractive : EntityDebuggerStyles.ComponentRequired; + GUILayout.Label(filterTypes[i].GetManagedType().Name, style); + } + } + if (filterCount == 0) + GUILayout.Label("none"); + if (GUILayout.Button("Edit")) + { + ComponentTypeChooser.Open(GUIUtility.GUIToScreenPoint(GUILayoutUtility.GetLastRect().position), filterTypes, selectedFilterTypes, ComponentFilterChanged); + } + if (filterCount > 0) + { + if (GUILayout.Button("Clear")) + { + for (var i = 0; i < selectedFilterTypes.Count; ++i) + { + selectedFilterTypes[i] = false; + } + ComponentFilterChanged(); + } + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + + internal ComponentGroup GetExistingGroup(ComponentType[] components) + { + foreach (var existingGroup in componentGroups) + { + if (existingGroup.CompareComponents(components)) + return existingGroup; + } + + return null; + } + + internal ComponentGroup GetComponentGroup(ComponentType[] components) + { + var group = GetExistingGroup(components); + if (group != null) + return group; + group = getWorldSelection().GetExistingManager().CreateComponentGroup(components); + componentGroups.Add(group); + + return group; + } + + private void ComponentFilterChanged() + { + var selectedTypes = new List(); + for (var i = 0; i < selectedFilterTypes.Count; ++i) + { + if (selectedFilterTypes[i]) + selectedTypes.Add(filterTypes[i]); + } + var group = GetComponentGroup(selectedTypes.ToArray()); + setFilter(group); + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/ComponentTypeFilterUI.cs.meta b/Unity.Entities.Editor/EntityDebugger/ComponentTypeFilterUI.cs.meta new file mode 100644 index 00000000..810eb960 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/ComponentTypeFilterUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 83f8526a74b4d4745a6cec5a17b90021 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/ComponentTypeListView.cs b/Unity.Entities.Editor/EntityDebugger/ComponentTypeListView.cs new file mode 100644 index 00000000..90b960a7 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/ComponentTypeListView.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + + public class ComponentTypeListView : TreeView + { + private List types; + private List typeSelections; + + private CallbackAction callback; + + public ComponentTypeListView(TreeViewState state, List types, List typeSelections, CallbackAction callback) : base(state) + { + this.callback = callback; + this.types = types; + this.typeSelections = typeSelections; + Reload(); + } + + protected override TreeViewItem BuildRoot() + { + var root = new TreeViewItem { id = -1, depth = -1, displayName = "Root" }; + if (types.Count == 0) + { + root.AddChild(new TreeViewItem { id = 1, displayName = "No types" }); + } + else + { + for (var i = 0; i < types.Count; ++i) + { + var displayName = (types[i].AccessModeType == ComponentType.AccessMode.Subtractive ? "-" : "") + types[i].GetManagedType().Name; + root.AddChild(new TreeViewItem {id = i, displayName = displayName}); + } + } + + SetupDepthsFromParentsAndChildren(root); + return root; + } + + protected override void RowGUI(RowGUIArgs args) + { + EditorGUI.BeginChangeCheck(); + typeSelections[args.item.id] = EditorGUI.Toggle(args.rowRect, typeSelections[args.item.id]); + var style = types[args.item.id].AccessModeType == ComponentType.AccessMode.Subtractive + ? EntityDebuggerStyles.ComponentSubtractive + : EntityDebuggerStyles.ComponentRequired; + var indent = GetContentIndent(args.item); + var content = new GUIContent(types[args.item.id].GetManagedType().Name); + var labelRect = args.rowRect; + labelRect.xMin = labelRect.xMin + indent; + labelRect.size = style.CalcSize(content); + GUI.Label(labelRect, content, style); + if (EditorGUI.EndChangeCheck()) + { + callback(); + } + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/ComponentTypeListView.cs.meta b/Unity.Entities.Editor/EntityDebugger/ComponentTypeListView.cs.meta new file mode 100644 index 00000000..795af663 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/ComponentTypeListView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa32975da3dc54a7a85fc49012566fe0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/EntityDebugger.cs b/Unity.Entities.Editor/EntityDebugger/EntityDebugger.cs new file mode 100644 index 00000000..94238b99 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntityDebugger.cs @@ -0,0 +1,414 @@ +using System.Linq; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using UnityEditor.IMGUI.Controls; + +namespace Unity.Entities.Editor +{ + public class EntityDebugger : EditorWindow + { + private const float kSystemListWidth = 350f; + +#if UNITY_2018_2_OR_NEWER + [MenuItem("Window/Debug/Entity Debugger", false)] +#else + [MenuItem("Window/Entity Debugger", false, 2017)] +#endif + static void OpenWindow() + { + GetWindow("Entity Debugger"); + } + + private static GUIStyle Box + { + get + { + if (box == null) + { + box = new GUIStyle(GUI.skin.box) + { + margin = new RectOffset(), + padding = new RectOffset(1, 0, 1, 0), + overflow = new RectOffset(0, 1, 0, 1) + }; + } + + return box; + } + } + + private static GUIStyle box; + + public ScriptBehaviourManager SystemSelection + { + get { return systemSelection; } + } + + public void SetSystemSelection(ScriptBehaviourManager manager, bool updateList, bool propagate) + { + systemSelection = manager; + if (updateList) + systemListView.SetSystemSelection(manager); + CreateComponentGroupListView(); + if (propagate) + { + if (systemSelection is ComponentSystemBase) + componentGroupListView.TouchSelection(); + else + ApplyAllEntitiesFilter(); + } + } + + private ScriptBehaviourManager systemSelection; + + public ComponentGroup ComponentGroupSelection + { + get { return componentGroupSelection; } + } + + public void SetComponentGroupSelection(ComponentGroup newSelection, bool updateList, bool propagate) + { + componentGroupSelection = newSelection; + if (updateList) + componentGroupListView.SetComponentGroupSelection(newSelection); + entityListView.SelectedComponentGroup = newSelection; + if (propagate) + entityListView.TouchSelection(); + } + + private ComponentGroup componentGroupSelection; + + public Entity EntitySelection + { + get { return selectionProxy.Entity; } + } + + public void SetEntitySelection(Entity newSelection, bool updateList) + { + if (updateList) + entityListView.SetEntitySelection(newSelection); + if (WorldSelection != null && newSelection != Entity.Null) + { + selectionProxy.SetEntity(WorldSelection, newSelection); + Selection.activeObject = selectionProxy; + } + else if (Selection.activeObject == selectionProxy) + { + Selection.activeObject = null; + } + } + + public static void SetAllSelections(World world, ComponentSystemBase system, ComponentGroup componentGroup, Entity entity) + { + if (Instance == null) + return; + Instance.SetWorldSelection(world, false); + Instance.SetSystemSelection(system, true, false); + Instance.SetComponentGroupSelection(componentGroup, true, false); + Instance.SetEntitySelection(entity, true); + Instance.entityListView.FrameSelection(); + } + + public static EntityDebugger Instance { get; set; } + + private EntitySelectionProxy selectionProxy; + + [SerializeField] private List componentGroupListStates = new List(); + [SerializeField] private List componentGroupListStateNames = new List(); + private ComponentGroupListView componentGroupListView; + + [SerializeField] private List systemListStates = new List(); + [SerializeField] private List systemListStateNames = new List(); + private SystemListView systemListView; + + [SerializeField] private TreeViewState entityListState = new TreeViewState(); + private EntityListView entityListView; + + private ComponentTypeFilterUI filterUI; + + private string[] worldNames => (from x in World.AllWorlds select x.Name).ToArray(); + + private void SelectWorldByName(string name, bool propagate) + { + foreach (var world in World.AllWorlds) + { + if (world.Name == name) + { + SetWorldSelection(world, propagate); + return; + } + } + + SetWorldSelection(null, propagate); + } + + public World WorldSelection + { + get + { + if (worldSelection != null && worldSelection.IsCreated) + return worldSelection; + return null; + } + } + + public void SetWorldSelection(World selection, bool propagate) + { + if (worldSelection != selection) + { + worldSelection = selection; + if (worldSelection != null) + { + lastSelectedWorldName = worldSelection.Name; + } + + CreateSystemListView(); + systemListView.multiColumnHeader.ResizeToFit(); + if (propagate) + systemListView.TouchSelection(); + } + } + + private void CreateEntityListView() + { + entityListView = new EntityListView(entityListState, ComponentGroupSelection, SetEntitySelection, () => WorldSelection); + } + + private void CreateSystemListView() + { + systemListView = SystemListView.CreateList(systemListStates, systemListStateNames, SetSystemSelection, () => WorldSelection); + } + + private void CreateComponentGroupListView() + { + componentGroupListView = ComponentGroupListView.CreateList(SystemSelection as ComponentSystemBase, componentGroupListStates, componentGroupListStateNames, SetComponentGroupSelection, () => WorldSelection); + } + + private World worldSelection; + [SerializeField] private string lastSelectedWorldName; + + private int selectedWorldIndex + { + get { return World.AllWorlds.IndexOf(WorldSelection); } + set + { + if (value >= 0 && value < World.AllWorlds.Count) + SetWorldSelection(World.AllWorlds[value], true); + } + } + + private readonly string[] noWorldsName = new[] {"No worlds"}; + + void OnEnable() + { + Instance = this; + selectionProxy = ScriptableObject.CreateInstance(); + selectionProxy.hideFlags = HideFlags.HideAndDontSave; + filterUI = new ComponentTypeFilterUI(SetAllEntitiesFilter, () => WorldSelection); + CreateSystemListView(); + CreateComponentGroupListView(); + CreateEntityListView(); + systemListView.TouchSelection(); + EditorApplication.playModeStateChanged += OnPlayModeStateChange; + } + + private void OnDisable() + { + if (Instance == this) + Instance = null; + if (selectionProxy) + DestroyImmediate(selectionProxy); + + EditorApplication.playModeStateChanged -= OnPlayModeStateChange; + } + + void OnPlayModeStateChange(PlayModeStateChange change) + { + if (change == PlayModeStateChange.ExitingPlayMode) + SetAllEntitiesFilter(null); + if (change == PlayModeStateChange.ExitingPlayMode && Selection.activeObject == selectionProxy) + Selection.activeObject = null; + } + + private float lastUpdate; + + void Update() + { + systemListView.UpdateTimings(); + + systemListView.UpdateIfNecessary(); + componentGroupListView.UpdateIfNecessary(); + entityListView.UpdateIfNecessary(); + filterUI.GetTypes(); + + if (Time.realtimeSinceStartup > lastUpdate + 0.5f) + { + Repaint(); + } + } + + void WorldPopup() + { + if (World.AllWorlds.Count == 0) + { + var guiEnabled = GUI.enabled; + GUI.enabled = false; + EditorGUILayout.Popup(0, noWorldsName); + GUI.enabled = guiEnabled; + } + else + { + if (WorldSelection == null || !WorldSelection.IsCreated) + { + SelectWorldByName(lastSelectedWorldName, true); + if (WorldSelection == null) + { + SetWorldSelection(World.AllWorlds[0], true); + } + } + selectedWorldIndex = EditorGUILayout.Popup(selectedWorldIndex, worldNames); + } + } + + void SystemList() + { + var rect = GUIHelpers.GetExpandingRect(); + if (World.AllWorlds.Count != 0) + { + systemListView.OnGUI(rect); + } + else + { + GUIHelpers.ShowCenteredNotification(rect, "No systems (Try pushing Play)"); + } + } + + void SystemHeader() + { + GUILayout.BeginHorizontal(); + GUILayout.Label("Systems", EditorStyles.boldLabel); + GUILayout.FlexibleSpace(); + AlignHeader(WorldPopup); + GUILayout.EndHorizontal(); + } + + void EntityHeader() + { + if (WorldSelection == null) + return; + GUILayout.BeginHorizontal(); + if (SystemSelection == null) + { + GUILayout.Label("All Entities", EditorStyles.boldLabel); + } + else + { + var type = SystemSelection.GetType(); + AlignHeader(() => GUILayout.Label(type.Namespace, EditorStyles.label)); + GUILayout.Label(type.Name, EditorStyles.boldLabel); + GUILayout.FlexibleSpace(); + var system = SystemSelection as ComponentSystemBase; + if (system != null) + { + var running = system.Enabled && system.ShouldRunSystem(); + AlignHeader(() => GUILayout.Label($"running: {(running ? "yes" : "no")}")); + } + } + GUILayout.EndHorizontal(); + } + + void ComponentGroupList() + { + if (SystemSelection is ComponentSystemBase) + { + GUILayout.BeginVertical(Box, GUILayout.Height(componentGroupListView.Height + Box.padding.bottom + Box.padding.top)); + + componentGroupListView.OnGUI(GUIHelpers.GetExpandingRect()); + GUILayout.EndVertical(); + } + else if (WorldSelection != null) + { + filterUI.OnGUI(); + } + } + + private ComponentGroup filterGroup; + + public void SetAllEntitiesFilter(ComponentGroup componentGroup) + { + filterGroup = componentGroup; + if (WorldSelection == null || SystemSelection is ComponentSystemBase) + return; + ApplyAllEntitiesFilter(); + } + + private void ApplyAllEntitiesFilter() + { + SetComponentGroupSelection(filterGroup, false, true); + } + + void EntityList() + { + var showingAllEntities = !(SystemSelection is ComponentSystemBase); + var componentGroupHasEntities = ComponentGroupSelection != null && !ComponentGroupSelection.IsEmptyIgnoreFilter; + var somethingToShow = showingAllEntities || componentGroupHasEntities; + if (WorldSelection == null || !somethingToShow) + return; + GUILayout.BeginVertical(Box); + entityListView.OnGUI(GUIHelpers.GetExpandingRect()); + GUILayout.EndVertical(); + } + + void AlignHeader(System.Action header) + { + GUILayout.BeginVertical(); + GUILayout.Space(6f); + header(); + GUILayout.EndVertical(); + } + + private void OnSelectionChange() + { + if (Selection.activeObject != selectionProxy) + { + entityListView.SelectNothing(); + } + } + + void OnGUI() + { + if (Selection.activeObject == selectionProxy) + { + if (!selectionProxy.Exists) + { + Selection.activeObject = null; + entityListView.SelectNothing(); + } + } + + GUILayout.BeginHorizontal(); + + GUILayout.BeginVertical(GUILayout.Width(kSystemListWidth)); // begin System side + SystemHeader(); + + GUILayout.BeginVertical(Box); + SystemList(); + GUILayout.EndVertical(); + + GUILayout.EndVertical(); // end System side + + GUILayout.BeginVertical(GUILayout.Width(position.width - kSystemListWidth)); // begin Entity side + + EntityHeader(); + ComponentGroupList(); + EntityList(); + + GUILayout.EndVertical(); // end Entity side + + GUILayout.EndHorizontal(); + + lastUpdate = Time.realtimeSinceStartup; + } + } +} \ No newline at end of file diff --git a/Unity.Entities.Editor/EntityDebugger/EntityDebugger.cs.meta b/Unity.Entities.Editor/EntityDebugger/EntityDebugger.cs.meta new file mode 100644 index 00000000..4349029c --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntityDebugger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8bf5230d087064def9a2d1129c733014 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/EntityDebuggerStyles.cs b/Unity.Entities.Editor/EntityDebugger/EntityDebuggerStyles.cs new file mode 100644 index 00000000..07023cc7 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntityDebuggerStyles.cs @@ -0,0 +1,32 @@ + + +using UnityEditor; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + public static class EntityDebuggerStyles + { + + public static GUIStyle ComponentRequired => StyleAsset.styles[0]; + public static GUIStyle ComponentSubtractive => StyleAsset.styles[1]; + public static GUIStyle ComponentReadOnly => StyleAsset.styles[2]; + public static GUIStyle ComponentReadWrite => StyleAsset.styles[3]; + + private static GUIStyleAsset StyleAsset + { + get + { + if (!styleAsset) + { + styleAsset = AssetDatabase.LoadAssetAtPath( + "Packages/com.unity.entities/Unity.Entities.Editor/Resources/EntityDebuggerStyles.asset"); + } + + return styleAsset; + } + } + + private static GUIStyleAsset styleAsset; + } +} \ No newline at end of file diff --git a/Unity.Entities.Editor/EntityDebugger/EntityDebuggerStyles.cs.meta b/Unity.Entities.Editor/EntityDebugger/EntityDebuggerStyles.cs.meta new file mode 100644 index 00000000..c5a624df --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntityDebuggerStyles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f4bc9457a5484177922f65e551e061d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/EntityIMGUIVisitor.cs b/Unity.Entities.Editor/EntityDebugger/EntityIMGUIVisitor.cs new file mode 100644 index 00000000..aec82eaa --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntityIMGUIVisitor.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Entities.Properties; +using UnityEngine; +using Unity.Properties; +using Unity.Mathematics; +using UnityEditor; + +namespace Unity.Entities.Editor +{ + + public class EntityIMGUIVisitor : PropertyVisitor + , IPrimitivePropertyVisitor + , ICustomVisitPrimitives + , ICustomVisit + , ICustomVisit + , ICustomVisit + , ICustomVisit + , ICustomVisit + , ICustomVisit + , ICustomVisit + { + private static HashSet _primitiveTypes = new HashSet(); + + static EntityIMGUIVisitor() + { + foreach (var it in typeof(EntityIMGUIVisitor).GetInterfaces()) + { + if (it.IsGenericType && typeof(ICustomVisit<>) == it.GetGenericTypeDefinition()) + { + var genArgs = it.GetGenericArguments(); + if (genArgs.Length == 1) + { + _primitiveTypes.Add(genArgs[0]); + } + } + } + foreach (var it in typeof(PropertyVisitor).GetInterfaces()) + { + if (it.IsGenericType && typeof(ICustomVisit<>) == it.GetGenericTypeDefinition()) + { + var genArgs = it.GetGenericArguments(); + if (genArgs.Length == 1) + { + _primitiveTypes.Add(genArgs[0]); + } + } + } + } + + public HashSet SupportedPrimitiveTypes() + { + return _primitiveTypes; + } + + private class ComponentState + { + public ComponentState() + { + Showing = true; + } + public bool Showing { get; set; } + } + private Dictionary _states = new Dictionary(); + private PropertyPath _currentPath = new PropertyPath(); + + protected override void Visit(TValue value) + { + GUILayout.Label(Property.Name); + } + + public override void VisitEnum(ref TContainer container, VisitContext context) + { + VisitSetup(ref container, ref context); + + var t = typeof(TValue); + if (t.IsEnum) + { + var options = Enum.GetNames(t).ToArray(); + EditorGUILayout.Popup( + t.Name, + Array.FindIndex(options, name => name == context.Value.ToString()), + options); + } + } + + public override bool BeginContainer(ref TContainer container, VisitContext context) + { + VisitSetup(ref container, ref context); + EditorGUI.indentLevel++; + + _currentPath.Push(Property.Name, context.Index); + + if (typeof(TValue) == typeof(StructProxy)) + { + ComponentState state; + if (!_states.ContainsKey(_currentPath.ToString())) + { + _states[_currentPath.ToString()] = new ComponentState(); + } + state = _states[_currentPath.ToString()]; + + state.Showing = EditorGUILayout.Foldout(state.Showing, context.Property.Name); + + return state.Showing; + } + return true; + } + + public override void EndContainer(ref TContainer container, VisitContext context) + { + VisitSetup(ref container, ref context); + _currentPath.Pop(); + + EditorGUI.indentLevel--; + } + + public override bool BeginList(ref TContainer container, VisitContext context) + { + VisitSetup(ref container, ref context); + return true; + } + + public override void EndList(ref TContainer container, VisitContext context) + { + VisitSetup(ref container, ref context); + } + + void ICustomVisit.CustomVisit(Unity.Mathematics.quaternion q) + { + EditorGUILayout.Vector4Field(Property.Name, new Vector4(q.value.x, q.value.y, q.value.z, q.value.w)); + } + + void ICustomVisit.CustomVisit(float2 f) + { + EditorGUILayout.Vector2Field(Property.Name, (Vector2) f); + } + + void ICustomVisit.CustomVisit(float3 f) + { + EditorGUILayout.Vector3Field(Property.Name, (float3)f); + } + + void ICustomVisit.CustomVisit(float4 f) + { + EditorGUILayout.Vector4Field(Property.Name, (float4)f); + } + + void ICustomVisit.CustomVisit(float2x2 f) + { + GUILayout.Label(Property.Name); + EditorGUILayout.Vector2Field("", (Vector2)f.m0); + EditorGUILayout.Vector2Field("", (Vector2)f.m1); + } + + void ICustomVisit.CustomVisit(float3x3 f) + { + GUILayout.Label(Property.Name); + EditorGUILayout.Vector3Field("", (Vector3)f.m0); + EditorGUILayout.Vector3Field("", (Vector3)f.m1); + EditorGUILayout.Vector3Field("", (Vector3)f.m2); + } + + void ICustomVisit.CustomVisit(float4x4 f) + { + GUILayout.Label(Property.Name); + EditorGUILayout.Vector4Field("", (Vector4)f.m0); + EditorGUILayout.Vector4Field("", (Vector4)f.m1); + EditorGUILayout.Vector4Field("", (Vector4)f.m2); + EditorGUILayout.Vector4Field("", (Vector4)f.m3); + } + + #region ICustomVisitPrimitives + + void ICustomVisit.CustomVisit(sbyte f) + { + DoField(Property, f, (label, val) => (sbyte)Mathf.Clamp(EditorGUILayout.IntField(label, val), sbyte.MinValue, sbyte.MaxValue)); + } + + void ICustomVisit.CustomVisit(short f) + { + DoField(Property, f, (label, val) => (short)Mathf.Clamp(EditorGUILayout.IntField(label, val), short.MinValue, short.MaxValue)); + } + + void ICustomVisit.CustomVisit(int f) + { + DoField(Property, f, (label, val) => EditorGUILayout.IntField(label, val)); + } + + void ICustomVisit.CustomVisit(long f) + { + DoField(Property, f, (label, val) => EditorGUILayout.LongField(label, val)); + } + + void ICustomVisit.CustomVisit(byte f) + { + DoField(Property, f, (label, val) => (byte)Mathf.Clamp(EditorGUILayout.IntField(label, val), byte.MinValue, byte.MaxValue)); + } + + void ICustomVisit.CustomVisit(ushort f) + { + DoField(Property, f, (label, val) => (ushort)Mathf.Clamp(EditorGUILayout.IntField(label, val), ushort.MinValue, ushort.MaxValue)); + } + + void ICustomVisit.CustomVisit(uint f) + { + DoField(Property, f, (label, val) => (uint)Mathf.Clamp(EditorGUILayout.LongField(label, val), uint.MinValue, uint.MaxValue)); + } + + void ICustomVisit.CustomVisit(ulong f) + { + DoField(Property, f, (label, val) => + { + var text = EditorGUILayout.TextField(label, val.ToString()); + ulong num; + ulong.TryParse(text, out num); + return num; + }); + } + + void ICustomVisit.CustomVisit(float f) + { + DoField(Property, f, (label, val) => EditorGUILayout.FloatField(label, val)); + } + + void ICustomVisit.CustomVisit(double f) + { + DoField(Property, f, (label, val) => EditorGUILayout.DoubleField(label, val)); + } + + void ICustomVisit.CustomVisit(bool f) + { + DoField(Property, f, (label, val) => EditorGUILayout.Toggle(label, val)); + } + + void ICustomVisit.CustomVisit(char f) + { + DoField(Property, f, (label, val) => + { + var text = EditorGUILayout.TextField(label, val.ToString()); + var c = (string.IsNullOrEmpty(text) ? '\0' : text[0]); + return c; + }); + } + + void ICustomVisit.CustomVisit(string f) + { + if (Property == null) + { + return; + } + GUILayout.Label(f, EditorStyles.boldLabel); + } + #endregion + + private void DoField(IProperty property, TValue value, Func onGUI) + { + if (property == null) + { + return; + } + + var previous = value; + onGUI(new GUIContent(property.Name), previous); + +#if ENABLE_PROPERTY_SET + var T = property.GetType(); + var typedProperty = Convert.ChangeType(property, T); + + if (!property.IsReadOnly && typedProperty != null) + { + // TODO doesn not work, ref container & container access + T.GetMethod("SetValue").Invoke(property, new object[] { container, v }); + } +#endif + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/EntityIMGUIVisitor.cs.meta b/Unity.Entities.Editor/EntityDebugger/EntityIMGUIVisitor.cs.meta new file mode 100644 index 00000000..fb8aa5c2 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntityIMGUIVisitor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 447f9816f681b4e6aaf53710b2a63966 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/EntityListView.cs b/Unity.Entities.Editor/EntityDebugger/EntityListView.cs new file mode 100644 index 00000000..70d4b567 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntityListView.cs @@ -0,0 +1,156 @@ +using UnityEditor.IMGUI.Controls; +using System.Collections.Generic; +using Unity.Collections; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + + public delegate void EntitySelectionCallback(Entity selection, bool updateList); + public delegate World WorldSelectionGetter(); + + public class EntityListView : TreeView { + private readonly Dictionary entitiesById = new Dictionary(); + + public ComponentGroup SelectedComponentGroup + { + get { return selectedComponentGroup; } + set + { + if (selectedComponentGroup != value) + { + selectedComponentGroup = value; + Reload(); + } + } + } + private ComponentGroup selectedComponentGroup; + int cachedVersion; + + private EntitySelectionCallback setEntitySelection; + private WorldSelectionGetter getWorldSelection; + + public EntityListView(TreeViewState state, ComponentGroup componentGroup, EntitySelectionCallback entitySelectionCallback, WorldSelectionGetter getWorldSelection) : base(state) + { + this.setEntitySelection = entitySelectionCallback; + this.getWorldSelection = getWorldSelection; + SelectedComponentGroup = componentGroup; + Reload(); + } + + public void UpdateIfNecessary() + { + if (getWorldSelection() == null) + return; + if (selectedComponentGroup == null) + { + if (getWorldSelection().GetExistingManager().Version != cachedVersion) + Reload(); + } + else if (selectedComponentGroup.GetCombinedComponentOrderVersion() != cachedVersion) + Reload(); + } + + private TreeViewItem CreateEntityItem(Entity entity) + { + entitiesById.Add(entity.Index, entity); + return new TreeViewItem { id = entity.Index }; + } + + protected override TreeViewItem BuildRoot() + { + entitiesById.Clear(); + var managerId = -1; + var root = new TreeViewItem { id = managerId--, depth = -1, displayName = "Root" }; + if (getWorldSelection() == null) + { + root.AddChild(new TreeViewItem { id = managerId, displayName = "No world selected"}); + cachedVersion = -1; + } + else + { + if (SelectedComponentGroup == null) + { + var entityManager = getWorldSelection().GetExistingManager(); + var array = entityManager.GetAllEntities(Allocator.Temp); + for (var i = 0; i < array.Length; ++i) + root.AddChild(CreateEntityItem(array[i])); + array.Dispose(); + cachedVersion = entityManager.Version; + } + else + { + getWorldSelection().GetExistingManager().CompleteAllJobs(); + var entityArray = SelectedComponentGroup.GetEntityArray(); + for (var i = 0; i < entityArray.Length; ++i) + root.AddChild(CreateEntityItem(entityArray[i])); + cachedVersion = SelectedComponentGroup.GetCombinedComponentOrderVersion(); + } + + if (entitiesById.Count == 0) + { + root.AddChild(new TreeViewItem { id = managerId, displayName = "No Entities"}); + } + SetupDepthsFromParentsAndChildren(root); + } + + return root; + } + + public override void OnGUI(Rect rect) + { + if (getWorldSelection()?.GetExistingManager()?.IsCreated == true) + base.OnGUI(rect); + } + + protected override void RowGUI(RowGUIArgs args) + { + if (args.item.displayName == null) + args.label = args.item.displayName = $"Entity {entitiesById[args.item.id].Index.ToString()}"; + base.RowGUI(args); + } + + protected override void SelectionChanged(IList selectedIds) + { + if (selectedIds.Count > 0) + { + if (entitiesById.ContainsKey(selectedIds[0])) + setEntitySelection(entitiesById[selectedIds[0]], false); + } + else + { + setEntitySelection(Entity.Null, false); + } + } + + protected override bool CanMultiSelect(TreeViewItem item) + { + return false; + } + + public void SelectNothing() + { + SetSelection(new List()); + } + + public void SetEntitySelection(Entity entitySelection) + { + if (entitySelection != Entity.Null && getWorldSelection().GetExistingManager().Exists(entitySelection)) + SetSelection(new List{entitySelection.Index}); + } + + public void TouchSelection() + { + SetSelection(GetSelection(), TreeViewSelectionOptions.FireSelectionChanged); + } + + public void FrameSelection() + { + var selection = GetSelection(); + if (selection.Count > 0) + { + FrameItem(selection[0]); + } + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/EntityListView.cs.meta b/Unity.Entities.Editor/EntityDebugger/EntityListView.cs.meta new file mode 100644 index 00000000..4a2944bf --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntityListView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5813625fef1447389737b86a3bc5d98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxy.cs b/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxy.cs new file mode 100644 index 00000000..410d2ffd --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxy.cs @@ -0,0 +1,23 @@ +using Unity.Entities.Properties; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + public class EntitySelectionProxy : ScriptableObject + { + public EntityContainer Container { get; private set; } + public Entity Entity { get; private set; } + public EntityManager EntityManager { get; private set; } + public World World { get; private set; } + + public bool Exists => EntityManager != null && EntityManager.IsCreated && EntityManager.Exists(Entity); + + public void SetEntity(World world, Entity entity) + { + this.World = world; + this.Entity = entity; + this.EntityManager = world.GetExistingManager(); + this.Container = new EntityContainer(EntityManager, Entity); + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxy.cs.meta b/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxy.cs.meta new file mode 100644 index 00000000..5e3944b5 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c05f47662cb2942e5beed3cb48d4608e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxyEditor.cs b/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxyEditor.cs new file mode 100644 index 00000000..8c822780 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxyEditor.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + [CustomEditor(typeof(EntitySelectionProxy))] + public class EntitySelectionProxyEditor : UnityEditor.Editor + { + private EntityIMGUIVisitor visitor; + + [SerializeField] private SystemInclusionList inclusionList; + + void OnEnable() + { + visitor = new EntityIMGUIVisitor(); + inclusionList = new SystemInclusionList(); + } + + public override void OnInspectorGUI() + { + var targetProxy = (EntitySelectionProxy) target; + if (!targetProxy.Exists) + return; + var container = targetProxy.Container; + targetProxy.Container.PropertyBag.Visit(ref container, visitor); + + GUI.enabled = true; + + inclusionList.OnGUI(targetProxy.World, targetProxy.Entity); + } + + public override bool RequiresConstantRepaint() + { + return true; + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxyEditor.cs.meta b/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxyEditor.cs.meta new file mode 100644 index 00000000..d0a09142 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/EntitySelectionProxyEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: feecbb3ccc8544b7e9008861dae1d82e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/EntityDebugger/SystemListView.cs b/Unity.Entities.Editor/EntityDebugger/SystemListView.cs new file mode 100644 index 00000000..c77e3c6a --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/SystemListView.cs @@ -0,0 +1,330 @@ +using System; +using UnityEditor.IMGUI.Controls; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Profiling; + +namespace Unity.Entities.Editor +{ + + public delegate void SystemSelectionCallback(ScriptBehaviourManager manager, bool updateList, bool propagate); + + public class SystemListView : TreeView + { + private class AverageRecorder + { + private readonly Recorder recorder; + private int frameCount; + private int totalNanoseconds; + private float lastReading; + + public AverageRecorder(Recorder recorder) + { + this.recorder = recorder; + } + + public void Update() + { + ++frameCount; + totalNanoseconds += (int)recorder.elapsedNanoseconds; + } + + public float ReadMilliseconds() + { + if (frameCount > 0) + { + lastReading = (totalNanoseconds/1e6f) / frameCount; + frameCount = totalNanoseconds = 0; + } + + return lastReading; + } + } + private readonly Dictionary> managersByGroup = new Dictionary>(); + private readonly List floatingManagers = new List(); + private readonly Dictionary managersById = new Dictionary(); + private readonly Dictionary recordersByManager = new Dictionary(); + + private const float kToggleWidth = 22f; + private const float kTimingWidth = 70f; + + private readonly SystemSelectionCallback systemSelectionCallback; + private readonly WorldSelectionGetter getWorldSelection; + + private int systemVersion; + + private static GUIStyle RightAlignedLabel + { + get + { + if (rightAlignedText == null) + { + rightAlignedText = new GUIStyle(GUI.skin.label); + rightAlignedText.alignment = TextAnchor.MiddleRight; + } + + return rightAlignedText; + } + } + + private static GUIStyle rightAlignedText; + + private static MultiColumnHeaderState GetHeaderState() + { + var columns = new[] + { + new MultiColumnHeaderState.Column + { + headerContent = GUIContent.none, + contextMenuText = "Enabled", + headerTextAlignment = TextAlignment.Left, + canSort = false, + width = kToggleWidth, + minWidth = kToggleWidth, + maxWidth = kToggleWidth, + autoResize = false, + allowToggleVisibility = false + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("System Name"), + headerTextAlignment = TextAlignment.Left, + sortingArrowAlignment = TextAlignment.Right, + canSort = true, + sortedAscending = true, + width = 100, + minWidth = 100, + maxWidth = 2000, + autoResize = true, + allowToggleVisibility = false + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("main (ms)"), + headerTextAlignment = TextAlignment.Right, + canSort = false, + width = kTimingWidth, + minWidth = kTimingWidth, + maxWidth = kTimingWidth, + autoResize = false, + allowToggleVisibility = false + } + }; + + return new MultiColumnHeaderState(columns); + } + + private static TreeViewState GetStateForWorld(World world, List states, List stateNames) + { + if (world == null) + return new TreeViewState(); + + var currentWorldName = world.Name; + + var stateForCurrentWorld = states.Where((t, i) => stateNames[i] == currentWorldName).FirstOrDefault(); + if (stateForCurrentWorld != null) + return stateForCurrentWorld; + + stateForCurrentWorld = new TreeViewState(); + states.Add(stateForCurrentWorld); + stateNames.Add(currentWorldName); + return stateForCurrentWorld; + } + + public static SystemListView CreateList(List states, List stateNames, SystemSelectionCallback systemSelectionCallback, WorldSelectionGetter worldSelectionGetter) + { + var state = GetStateForWorld(worldSelectionGetter(), states, stateNames); + var header = new MultiColumnHeader(GetHeaderState()); + return new SystemListView(state, header, systemSelectionCallback, worldSelectionGetter); + } + + private SystemListView(TreeViewState state, MultiColumnHeader header, SystemSelectionCallback systemSelectionCallback, WorldSelectionGetter worldSelectionGetter) : base(state, header) + { + this.getWorldSelection = worldSelectionGetter; + this.systemSelectionCallback = systemSelectionCallback; + columnIndexForTreeFoldouts = 1; + Reload(); + } + + static int CompareSystem(ScriptBehaviourManager x, ScriptBehaviourManager y) + { + var xIsEntityManager = x is EntityManager; + var yIsEntityManager = y is EntityManager; + if (xIsEntityManager == yIsEntityManager) + { + return string.CompareOrdinal(x.GetType().Name, y.GetType().Name); + } + else + { + return xIsEntityManager ? -1 : 1; + } + } + + private TreeViewItem CreateManagerItem(int id, ScriptBehaviourManager manager) + { + managersById.Add(id, manager); + var recorder = Recorder.Get($"{getWorldSelection().Name} {manager.GetType().FullName}"); + recordersByManager.Add(manager, new AverageRecorder(recorder)); + recorder.enabled = true; + return new TreeViewItem { id = id, displayName = manager.GetType().Name.ToString() }; + } + + protected override TreeViewItem BuildRoot() + { + managersByGroup.Clear(); + managersById.Clear(); + floatingManagers.Clear(); + recordersByManager.Clear(); + + systemVersion = -1; + if (getWorldSelection() != null) + { + systemVersion = getWorldSelection().Version; + Dictionary allGroups; + Dictionary dependencies; + ScriptBehaviourUpdateOrder.CollectGroups(getWorldSelection().BehaviourManagers, out allGroups, out dependencies); + + foreach (var manager in getWorldSelection().BehaviourManagers) + { + var hasGroup = false; + foreach (var attributeData in manager.GetType().GetCustomAttributesData()) + { + if (attributeData.AttributeType == typeof(UpdateInGroupAttribute)) + { + var groupType = (Type) attributeData.ConstructorArguments[0].Value; + if (!managersByGroup.ContainsKey(groupType)) + managersByGroup[groupType] = new List{manager}; + else + managersByGroup[groupType].Add(manager); + hasGroup = true; + break; + } + } + + if (!hasGroup) + { + floatingManagers.Add(manager); + } + } + foreach (var managerSet in managersByGroup.Values) + { + managerSet.Sort(CompareSystem); + } + } + floatingManagers.Sort(CompareSystem); + + var currentID = 0; + var root = new TreeViewItem { id = currentID++, depth = -1, displayName = "Root" }; + if (managersByGroup.Count == 0 && floatingManagers.Count == 0) + { + root.AddChild(new TreeViewItem { id = currentID++, displayName = "No ComponentSystems Loaded"}); + } + else + { + foreach (var manager in floatingManagers) + root.AddChild(CreateManagerItem(currentID++, manager)); + + foreach (var group in (from g in managersByGroup.Keys orderby g.Name select g)) + { + var groupItem = new TreeViewItem { id = currentID++, displayName = group.Name }; + root.AddChild(groupItem); + foreach (var manager in managersByGroup[group]) + groupItem.AddChild(CreateManagerItem(currentID++, manager)); + } + SetupDepthsFromParentsAndChildren(root); + } + return root; + } + + protected override void RowGUI (RowGUIArgs args) + { + if (args.item.depth == -1) + return; + var item = args.item; + + var enabled = GUI.enabled; + + if (managersById.ContainsKey(item.id)) + { + var manager = managersById[item.id]; + var componentSystemBase = manager as ComponentSystemBase; + if (componentSystemBase != null) + { + var toggleRect = args.GetCellRect(0); + toggleRect.xMin = toggleRect.xMin + 4f; + componentSystemBase.Enabled = GUI.Toggle(toggleRect, componentSystemBase.Enabled, GUIContent.none); + } + GUI.enabled = componentSystemBase?.ShouldRunSystem() ?? true; + + var timingRect = args.GetCellRect(2); + var recorder = recordersByManager[manager]; + GUI.Label(timingRect, recorder.ReadMilliseconds().ToString("f2"), RightAlignedLabel); + } + + var indent = GetContentIndent(item); + var nameRect = args.GetCellRect(1); + nameRect.xMin = nameRect.xMin + indent; + GUI.Label(nameRect, item.displayName); + GUI.enabled = enabled; + } + + protected override void SelectionChanged(IList selectedIds) + { + if (selectedIds.Count > 0 && managersById.ContainsKey(selectedIds[0])) + { + systemSelectionCallback(managersById[selectedIds[0]], false, true); + } + else + { + systemSelectionCallback(null, false, true); + } + } + + protected override bool CanMultiSelect(TreeViewItem item) + { + return false; + } + + public void TouchSelection() + { + SetSelection(GetSelection(), TreeViewSelectionOptions.FireSelectionChanged); + } + + public void UpdateIfNecessary() + { + if (getWorldSelection() == null) + return; + if (getWorldSelection().Version != systemVersion) + Reload(); + } + + private int lastTimedFrame; + + public void UpdateTimings() + { + if (Time.frameCount == lastTimedFrame) + return; + + foreach (var recorder in recordersByManager.Values) + { + recorder.Update(); + } + + lastTimedFrame = Time.frameCount; + } + + public void SetSystemSelection(ScriptBehaviourManager manager) + { + foreach (var pair in managersById) + { + if (pair.Value == manager) + { + SetSelection(new List {pair.Key}); + return; + } + } + SetSelection(new List()); + } + } +} diff --git a/Unity.Entities.Editor/EntityDebugger/SystemListView.cs.meta b/Unity.Entities.Editor/EntityDebugger/SystemListView.cs.meta new file mode 100644 index 00000000..56e08628 --- /dev/null +++ b/Unity.Entities.Editor/EntityDebugger/SystemListView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d199aeb038f6e45f4b2c59b144063729 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/ExtraTypesProvider.cs b/Unity.Entities.Editor/ExtraTypesProvider.cs new file mode 100644 index 00000000..d5d768b8 --- /dev/null +++ b/Unity.Entities.Editor/ExtraTypesProvider.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +#if !UNITY_2018_2_OR_NEWER +using UnityEditor.Experimental.Build.Player; +#else +using UnityEditor.Build.Player; +#endif + +namespace Unity.Entities.Editor +{ + [InitializeOnLoad] + public sealed class ExtraTypesProvider + { + const string k_AssemblyName = "Unity.Entities"; + + static ExtraTypesProvider() + { + //@TODO: Only produce JobProcessComponentDataExtensions.JobStruct_Process1 + // if there is any use of that specific type in deployed code. + + PlayerBuildInterface.ExtraTypesProvider += () => + { + var extraTypes = new HashSet(); + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (!assembly.GetReferencedAssemblies().Any(a => a.Name.Contains(k_AssemblyName)) && + assembly.GetName().Name != k_AssemblyName) + continue; + + foreach (var type in assembly.GetTypes()) + { + if (typeof(IBaseJobProcessComponentData).IsAssignableFrom(type) && !type.IsAbstract) + { + var genericArgumentList = new List + { + type + }; + foreach (var @interface in type.GetInterfaces()) + { + if (@interface.Name.StartsWith("IJobProcessComponentData")) + genericArgumentList.AddRange(@interface.GetGenericArguments()); + } + var genericArgs = genericArgumentList.ToArray(); + int argCount = genericArgs.Length - 1; + + if (argCount == 1) + { + var generatedType = typeof(JobProcessComponentDataExtensions.JobStruct_Process1<,>).MakeGenericType(genericArgs); + extraTypes.Add(generatedType.ToString()); + } + else if (argCount == 2) + { + var generatedType = typeof(JobProcessComponentDataExtensions.JobStruct_Process2<,,>).MakeGenericType(genericArgs); + extraTypes.Add(generatedType.ToString()); + } + else if (argCount == 3) + { + var generatedType = typeof(JobProcessComponentDataExtensions.JobStruct_Process3<,,,>).MakeGenericType(genericArgs); + extraTypes.Add(generatedType.ToString()); + } + } + } + } + + return extraTypes; + }; + } + } +} \ No newline at end of file diff --git a/Unity.Entities.Editor/ExtraTypesProvider.cs.meta b/Unity.Entities.Editor/ExtraTypesProvider.cs.meta new file mode 100644 index 00000000..cc16081a --- /dev/null +++ b/Unity.Entities.Editor/ExtraTypesProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 70c32833605b3a64aaa91d70a5a055ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/GUIHelpers.cs b/Unity.Entities.Editor/GUIHelpers.cs new file mode 100644 index 00000000..21db24ac --- /dev/null +++ b/Unity.Entities.Editor/GUIHelpers.cs @@ -0,0 +1,25 @@ +using UnityEngine; + +namespace Unity.Entities.Editor +{ + public static class GUIHelpers { + + public static void ShowCenteredNotification(Rect area, string message) + { + GUILayout.BeginArea(area); + GUILayout.FlexibleSpace(); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label(message); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.EndArea(); + } + + public static Rect GetExpandingRect() + { + return GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); + } + } +} \ No newline at end of file diff --git a/Unity.Entities.Editor/GUIHelpers.cs.meta b/Unity.Entities.Editor/GUIHelpers.cs.meta new file mode 100644 index 00000000..738f996b --- /dev/null +++ b/Unity.Entities.Editor/GUIHelpers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f1282463d42849eeb92f59a2576eedf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/GUIStyleAsset.cs b/Unity.Entities.Editor/GUIStyleAsset.cs new file mode 100644 index 00000000..aff811d6 --- /dev/null +++ b/Unity.Entities.Editor/GUIStyleAsset.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace Unity.Entities.Editor +{ +// Commented out to avoid cluttering Create Asset menu +// [CreateAssetMenu(fileName = "Styles.asset", menuName = "GUI Style Asset", order = 600)] + public class GUIStyleAsset : ScriptableObject + { + + public GUIStyle[] styles; + } +} \ No newline at end of file diff --git a/Unity.Entities.Editor/GUIStyleAsset.cs.meta b/Unity.Entities.Editor/GUIStyleAsset.cs.meta new file mode 100644 index 00000000..ea0bb32a --- /dev/null +++ b/Unity.Entities.Editor/GUIStyleAsset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08f85c546369d4feba08f4c4aac7a492 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/GameObjectEntityEditor.cs b/Unity.Entities.Editor/GameObjectEntityEditor.cs new file mode 100644 index 00000000..06f822c6 --- /dev/null +++ b/Unity.Entities.Editor/GameObjectEntityEditor.cs @@ -0,0 +1,33 @@ + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + [CustomEditor(typeof(GameObjectEntity))] + public class GameObjectEntityEditor : UnityEditor.Editor + { + [SerializeField] private SystemInclusionList inclusionList; + + private void OnEnable() + { + inclusionList = new SystemInclusionList(); + } + + public override void OnInspectorGUI() + { + var gameObjectEntity = (GameObjectEntity) target; + if (gameObjectEntity.EntityManager == null || !gameObjectEntity.EntityManager.IsCreated || !gameObjectEntity.EntityManager.Exists(gameObjectEntity.Entity)) + return; + + inclusionList.OnGUI(World.Active, gameObjectEntity.Entity); + } + + public override bool RequiresConstantRepaint() + { + return true; + } + } +} diff --git a/Unity.Entities.Editor/GameObjectEntityEditor.cs.meta b/Unity.Entities.Editor/GameObjectEntityEditor.cs.meta new file mode 100644 index 00000000..9242d4af --- /dev/null +++ b/Unity.Entities.Editor/GameObjectEntityEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d32689a9098949488c0f1c871f0b517 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources.meta b/Unity.Entities.Editor/Resources.meta new file mode 100644 index 00000000..a58ee487 --- /dev/null +++ b/Unity.Entities.Editor/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 934984215545149acaa747df37dde4df +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/EntityDebuggerStyles.asset b/Unity.Entities.Editor/Resources/EntityDebuggerStyles.asset new file mode 100644 index 00000000..b5b58e6f --- /dev/null +++ b/Unity.Entities.Editor/Resources/EntityDebuggerStyles.asset @@ -0,0 +1,282 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 08f85c546369d4feba08f4c4aac7a492, type: 3} + m_Name: EntityDebuggerStyles + m_EditorClassIdentifier: + styles: + - m_Name: EDB Component Required + m_Normal: + m_Background: {fileID: 2800000, guid: be6cd60ffab6b474287e370c70dc025e, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 9ccb57e130aff40b6ab8de7e07dae017, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 7 + m_Right: 7 + m_Top: 7 + m_Bottom: 7 + m_Margin: + m_Left: 2 + m_Right: 2 + m_Top: 1 + m_Bottom: 1 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 5 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: EDB Component Subtractive + m_Normal: + m_Background: {fileID: 2800000, guid: 5c5dc94ec202146e38ac04453eb79c99, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 100faf8bf1af24fd58b397cfbe76bacb, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 10 + m_Right: 7 + m_Top: 7 + m_Bottom: 7 + m_Margin: + m_Left: 2 + m_Right: 2 + m_Top: 1 + m_Bottom: 1 + m_Padding: + m_Left: 11 + m_Right: 6 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 5 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: EDB Component ReadOnly + m_Normal: + m_Background: {fileID: 2800000, guid: b1f7cc32bff7e473f88f927d7c4b02ec, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: cf443572d2e634592851ed589ebe05ea, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 10 + m_Right: 7 + m_Top: 7 + m_Bottom: 7 + m_Margin: + m_Left: 2 + m_Right: 2 + m_Top: 1 + m_Bottom: 1 + m_Padding: + m_Left: 11 + m_Right: 6 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 5 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: EDB Component ReadWrite + m_Normal: + m_Background: {fileID: 2800000, guid: 6c23fd4be92a44866992f209c8bb4ccc, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 9dd49e167124549df8b8b5024530ea42, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 12 + m_Right: 7 + m_Top: 7 + m_Bottom: 7 + m_Margin: + m_Left: 2 + m_Right: 2 + m_Top: 1 + m_Bottom: 1 + m_Padding: + m_Left: 13 + m_Right: 6 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 5 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 0 + m_StretchHeight: 0 diff --git a/Unity.Entities.Editor/Resources/EntityDebuggerStyles.asset.meta b/Unity.Entities.Editor/Resources/EntityDebuggerStyles.asset.meta new file mode 100644 index 00000000..ca527357 --- /dev/null +++ b/Unity.Entities.Editor/Resources/EntityDebuggerStyles.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c988f5b48928e401494c7358ea4d2d8c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/ReadOnly.png b/Unity.Entities.Editor/Resources/ReadOnly.png new file mode 100644 index 00000000..b24092e9 Binary files /dev/null and b/Unity.Entities.Editor/Resources/ReadOnly.png differ diff --git a/Unity.Entities.Editor/Resources/ReadOnly.png.meta b/Unity.Entities.Editor/Resources/ReadOnly.png.meta new file mode 100644 index 00000000..10ad1cbc --- /dev/null +++ b/Unity.Entities.Editor/Resources/ReadOnly.png.meta @@ -0,0 +1,106 @@ +fileFormatVersion: 2 +guid: b1f7cc32bff7e473f88f927d7c4b02ec +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 5 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/ReadOnly@2x.png b/Unity.Entities.Editor/Resources/ReadOnly@2x.png new file mode 100644 index 00000000..83b42bea Binary files /dev/null and b/Unity.Entities.Editor/Resources/ReadOnly@2x.png differ diff --git a/Unity.Entities.Editor/Resources/ReadOnly@2x.png.meta b/Unity.Entities.Editor/Resources/ReadOnly@2x.png.meta new file mode 100644 index 00000000..d5fd4a2a --- /dev/null +++ b/Unity.Entities.Editor/Resources/ReadOnly@2x.png.meta @@ -0,0 +1,106 @@ +fileFormatVersion: 2 +guid: cf443572d2e634592851ed589ebe05ea +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 5 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/ReadWrite.png b/Unity.Entities.Editor/Resources/ReadWrite.png new file mode 100644 index 00000000..e5ae5070 Binary files /dev/null and b/Unity.Entities.Editor/Resources/ReadWrite.png differ diff --git a/Unity.Entities.Editor/Resources/ReadWrite.png.meta b/Unity.Entities.Editor/Resources/ReadWrite.png.meta new file mode 100644 index 00000000..8566add7 --- /dev/null +++ b/Unity.Entities.Editor/Resources/ReadWrite.png.meta @@ -0,0 +1,106 @@ +fileFormatVersion: 2 +guid: 6c23fd4be92a44866992f209c8bb4ccc +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 5 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/ReadWrite@2x.png b/Unity.Entities.Editor/Resources/ReadWrite@2x.png new file mode 100644 index 00000000..719f4f77 Binary files /dev/null and b/Unity.Entities.Editor/Resources/ReadWrite@2x.png differ diff --git a/Unity.Entities.Editor/Resources/ReadWrite@2x.png.meta b/Unity.Entities.Editor/Resources/ReadWrite@2x.png.meta new file mode 100644 index 00000000..7921ce18 --- /dev/null +++ b/Unity.Entities.Editor/Resources/ReadWrite@2x.png.meta @@ -0,0 +1,106 @@ +fileFormatVersion: 2 +guid: 9dd49e167124549df8b8b5024530ea42 +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 5 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/Required.png b/Unity.Entities.Editor/Resources/Required.png new file mode 100644 index 00000000..fb242076 Binary files /dev/null and b/Unity.Entities.Editor/Resources/Required.png differ diff --git a/Unity.Entities.Editor/Resources/Required.png.meta b/Unity.Entities.Editor/Resources/Required.png.meta new file mode 100644 index 00000000..00523af3 --- /dev/null +++ b/Unity.Entities.Editor/Resources/Required.png.meta @@ -0,0 +1,106 @@ +fileFormatVersion: 2 +guid: be6cd60ffab6b474287e370c70dc025e +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 5 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/Required@2x.png b/Unity.Entities.Editor/Resources/Required@2x.png new file mode 100644 index 00000000..ab866273 Binary files /dev/null and b/Unity.Entities.Editor/Resources/Required@2x.png differ diff --git a/Unity.Entities.Editor/Resources/Required@2x.png.meta b/Unity.Entities.Editor/Resources/Required@2x.png.meta new file mode 100644 index 00000000..81ebdc40 --- /dev/null +++ b/Unity.Entities.Editor/Resources/Required@2x.png.meta @@ -0,0 +1,106 @@ +fileFormatVersion: 2 +guid: 9ccb57e130aff40b6ab8de7e07dae017 +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 5 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/Subtractive.png b/Unity.Entities.Editor/Resources/Subtractive.png new file mode 100644 index 00000000..3961ef92 Binary files /dev/null and b/Unity.Entities.Editor/Resources/Subtractive.png differ diff --git a/Unity.Entities.Editor/Resources/Subtractive.png.meta b/Unity.Entities.Editor/Resources/Subtractive.png.meta new file mode 100644 index 00000000..be14d439 --- /dev/null +++ b/Unity.Entities.Editor/Resources/Subtractive.png.meta @@ -0,0 +1,106 @@ +fileFormatVersion: 2 +guid: 5c5dc94ec202146e38ac04453eb79c99 +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 5 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Resources/Subtractive@2x.png b/Unity.Entities.Editor/Resources/Subtractive@2x.png new file mode 100644 index 00000000..a926f2bf Binary files /dev/null and b/Unity.Entities.Editor/Resources/Subtractive@2x.png differ diff --git a/Unity.Entities.Editor/Resources/Subtractive@2x.png.meta b/Unity.Entities.Editor/Resources/Subtractive@2x.png.meta new file mode 100644 index 00000000..664e7d09 --- /dev/null +++ b/Unity.Entities.Editor/Resources/Subtractive@2x.png.meta @@ -0,0 +1,106 @@ +fileFormatVersion: 2 +guid: 100faf8bf1af24fd58b397cfbe76bacb +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 5 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/SystemInclusionList.cs b/Unity.Entities.Editor/SystemInclusionList.cs new file mode 100644 index 00000000..99731394 --- /dev/null +++ b/Unity.Entities.Editor/SystemInclusionList.cs @@ -0,0 +1,101 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Unity.Entities.Editor +{ + [System.Serializable] + public class SystemInclusionList + { + private readonly List>> cachedMatches = new List>>(); + private bool repainted = true; + + [SerializeField] private bool showSystems; + + public void OnGUI(World world, Entity entity) + { + ++EditorGUI.indentLevel; + GUILayout.BeginVertical(GUI.skin.box); + showSystems = EditorGUILayout.Foldout(showSystems, "Used by Systems"); + + if (showSystems) + { + if (repainted == true) + { + cachedMatches.Clear(); + WorldDebuggingTools.MatchEntityInComponentGroups(world, entity, cachedMatches); + repainted = false; + } + + foreach (var pair in cachedMatches) + { + var type = pair.Item1.GetType(); + GUILayout.Label(new GUIContent(type.Name, type.AssemblyQualifiedName)); + ++EditorGUI.indentLevel; + foreach (var componentGroup in pair.Item2) + { + ComponentList(componentGroup.Types, EditorGUIUtility.currentViewWidth - 30f); + if (GUILayout.Button("Show", GUILayout.ExpandWidth(false))) + { + EntityDebugger.SetAllSelections(world, pair.Item1 as ComponentSystemBase, componentGroup, entity); + } + } + + --EditorGUI.indentLevel; + } + + if (Event.current.type == EventType.Repaint) + { + repainted = true; + } + } + GUILayout.EndVertical(); + + --EditorGUI.indentLevel; + } + + void ComponentList(ComponentType[] types, float width) + { + var sortedTypes = new List(types.Skip(1)); + sortedTypes.Sort(ComponentGroupListView.CompareTypes); + var styles = new List(sortedTypes.Count); + var names = new List(sortedTypes.Count); + var rects = new List(sortedTypes.Count); + var x = 0f; + var y = 0f; + for (var i = 0; i < sortedTypes.Count; ++i) // Skip Entity + { + var type = sortedTypes[i]; + var style = ComponentGroupListView.StyleForAccessMode(type.AccessModeType); + var content = new GUIContent(type.GetManagedType().Name); + var rect = new Rect(new Vector2(x, y), style.CalcSize(content)); + if (rect.xMax > width && x != 0f) + { + rect.x = 0f; + rect.y += rect.height + 2f; + } + + x = rect.xMax + 2f; + y = rect.y; + + styles.Add(style); + names.Add(content); + rects.Add(rect); + } + + var wholeRect = GUILayoutUtility.GetRect(width, rects.Last().yMax); + if (Event.current.type == EventType.Repaint) + { + for (var i = 0; i < rects.Count; ++i) + { + var rect = rects[i]; + rect.position += wholeRect.position; + styles[i].Draw(rect, names[i], false, false, false, false); + } + } + } + } +} diff --git a/Unity.Entities.Editor/SystemInclusionList.cs.meta b/Unity.Entities.Editor/SystemInclusionList.cs.meta new file mode 100644 index 00000000..70d8956f --- /dev/null +++ b/Unity.Entities.Editor/SystemInclusionList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2f16d2ff5177445292cf4472810917c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Editor/Unity.Entities.Editor.asmdef b/Unity.Entities.Editor/Unity.Entities.Editor.asmdef new file mode 100644 index 00000000..fde4e4ad --- /dev/null +++ b/Unity.Entities.Editor/Unity.Entities.Editor.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Unity.Entities.Editor", + "references": [ + "Unity.Entities", + "Unity.Entities.Hybrid", + "Unity.Entities.Properties", + "Unity.Mathematics", + "Unity.Properties" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [] +} diff --git a/Unity.Entities.Editor/Unity.Entities.Editor.asmdef.meta b/Unity.Entities.Editor/Unity.Entities.Editor.asmdef.meta new file mode 100644 index 00000000..69959bcc --- /dev/null +++ b/Unity.Entities.Editor/Unity.Entities.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c38fb62397af04c51b143415e1db6d90 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.Tests.meta b/Unity.Entities.Hybrid.Tests.meta new file mode 100644 index 00000000..e7f61b36 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 69f456f402843436686020360b28d97a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.Tests/ComponentGroupTransformAccessArrayTests.cs b/Unity.Entities.Hybrid.Tests/ComponentGroupTransformAccessArrayTests.cs new file mode 100644 index 00000000..0a357004 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/ComponentGroupTransformAccessArrayTests.cs @@ -0,0 +1,252 @@ +using NUnit.Framework; +using Unity.Entities; +using UnityEngine; +using UnityEngine.Jobs; + +namespace Unity.Entities.Tests +{ + public class ComponentGroupTransformAccessArrayTests : ECSTestsFixture + { + + TransformAccessArrayInjectionHook m_TransformAccessArrayInjectionHook = new TransformAccessArrayInjectionHook(); + ComponentArrayInjectionHook m_ComponentArrayInjectionHook = new ComponentArrayInjectionHook(); + + [OneTimeSetUp] + public void Init() + { + InjectionHookSupport.RegisterHook(m_ComponentArrayInjectionHook); + InjectionHookSupport.RegisterHook(m_TransformAccessArrayInjectionHook); + } + + [OneTimeTearDown] + public void Cleanup() + { + InjectionHookSupport.RegisterHook(m_TransformAccessArrayInjectionHook); + InjectionHookSupport.UnregisterHook(m_ComponentArrayInjectionHook); + } + + public ComponentGroupTransformAccessArrayTests() + { + Assert.IsTrue(Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobDebuggerEnabled, "JobDebugger must be enabled for these tests"); + } + + public struct TransformAccessArrayTestTag : IComponentData + { + } + public class TransformAccessArrayTestTagComponent : ComponentDataWrapper { } + + [Test] + public void EmptyTransformAccessArrayWorks() + { + var group = EmptySystem.GetComponentGroup(typeof(Transform), typeof(TransformAccessArrayTestTag)); + var ta = group.GetTransformAccessArray(); + Assert.AreEqual(0, ta.length); + } + [Test] + public void SingleItemTransformAccessArrayWorks() + { + var go = new GameObject(); + go.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnEnable(); + var group = EmptySystem.GetComponentGroup(typeof(Transform), typeof(TransformAccessArrayTestTag)); + var ta = group.GetTransformAccessArray(); + Assert.AreEqual(1, ta.length); + + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnDisable(); + Object.DestroyImmediate(go); + } + [Test] + public void AddAndGetNewTransformAccessArrayUpdatesContent() + { + var go = new GameObject(); + go.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnEnable(); + var group = EmptySystem.GetComponentGroup(typeof(Transform), typeof(TransformAccessArrayTestTag)); + var ta = group.GetTransformAccessArray(); + Assert.AreEqual(1, ta.length); + + var go2 = new GameObject(); + go2.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go2.GetComponent().OnEnable(); + ta = group.GetTransformAccessArray(); + Assert.AreEqual(2, ta.length); + + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnDisable(); + go2.GetComponent().OnDisable(); + Object.DestroyImmediate(go); + Object.DestroyImmediate(go2); + } + [Test] + // The atomic safety handle of TransformAccessArrays are not invalidated when injection changes, the array represents the transforms when you got it + public void AddAndUseOldTransformAccessArrayDoesNotUpdateContent() + { + var go = new GameObject(); + go.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnEnable(); + var group = EmptySystem.GetComponentGroup(typeof(Transform), typeof(TransformAccessArrayTestTag)); + var ta = group.GetTransformAccessArray(); + Assert.AreEqual(1, ta.length); + + var go2 = new GameObject(); + go2.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go2.GetComponent().OnEnable(); + Assert.AreEqual(1, ta.length); + + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnDisable(); + go2.GetComponent().OnDisable(); + Object.DestroyImmediate(go); + Object.DestroyImmediate(go2); + } + [Test] + public void DestroyAndGetNewTransformAccessArrayUpdatesContent() + { + var go = new GameObject(); + go.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnEnable(); + var go2 = new GameObject(); + go2.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go2.GetComponent().OnEnable(); + + var group = EmptySystem.GetComponentGroup(typeof(Transform), typeof(TransformAccessArrayTestTag)); + var ta = group.GetTransformAccessArray(); + Assert.AreEqual(2, ta.length); + + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnDisable(); + Object.DestroyImmediate(go); + + ta = group.GetTransformAccessArray(); + Assert.AreEqual(1, ta.length); + + // Execute in edit mode is not enabled so this has to be called manually right now + go2.GetComponent().OnDisable(); + Object.DestroyImmediate(go2); + } + [Test] + // The atomic safety handle of TransformAccessArrays are not invalidated when injection changes, the array represents the transforms when you got it + public void DestroyAndUseOldTransformAccessArrayDoesNotUpdateContent() + { + var go = new GameObject(); + go.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnEnable(); + var go2 = new GameObject(); + go2.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go2.GetComponent().OnEnable(); + + var group = EmptySystem.GetComponentGroup(typeof(Transform), typeof(TransformAccessArrayTestTag)); + var ta = group.GetTransformAccessArray(); + Assert.AreEqual(2, ta.length); + + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnDisable(); + Object.DestroyImmediate(go); + + Assert.AreEqual(2, ta.length); + + // Execute in edit mode is not enabled so this has to be called manually right now + go2.GetComponent().OnDisable(); + Object.DestroyImmediate(go2); + } + + [DisableAutoCreation] + public class GameObjectArrayWithTransformAccessSystem : ComponentSystem + { + public struct Group + { + public int Length; + public GameObjectArray gameObjects; + + public TransformAccessArray transforms; + } + + [Inject] + public Group group; + + protected override void OnUpdate() + { + } + + public new void UpdateInjectedComponentGroups() + { + base.UpdateInjectedComponentGroups(); + } + } + + [Test] + public void GameObjectArrayWorksWithTransformAccessArray() + { + var hook = new GameObjectArrayInjectionHook(); + InjectionHookSupport.RegisterHook(hook); + + var go = new GameObject("test"); + GameObjectEntity.AddToEntityManager(m_Manager, go); + + var manager = World.GetOrCreateManager(); + + manager.UpdateInjectedComponentGroups(); + + Assert.AreEqual(1, manager.group.Length); + Assert.AreEqual(go, manager.group.gameObjects[0]); + Assert.AreEqual(go, manager.group.transforms[0].gameObject); + + Object.DestroyImmediate (go); + + InjectionHookSupport.UnregisterHook(hook); + + TearDown(); + } + + [DisableAutoCreation] + public class TransformWithTransformAccessSystem : ComponentSystem + { + public struct Group + { + public int Length; + public ComponentArray transforms; + + public TransformAccessArray transformAccesses; + } + + [Inject] + public Group group; + + protected override void OnUpdate() + { + } + + public new void UpdateInjectedComponentGroups() + { + base.UpdateInjectedComponentGroups(); + } + } + + [Test] + public void TransformArrayWorksWithTransformAccessArray() + { + var go = new GameObject("test"); + GameObjectEntity.AddToEntityManager(m_Manager, go); + + var manager = World.GetOrCreateManager(); + + manager.UpdateInjectedComponentGroups(); + + Assert.AreEqual(1, manager.group.Length); + Assert.AreEqual(manager.group.transforms[0].gameObject, manager.group.transformAccesses[0].gameObject); + + Object.DestroyImmediate (go); + TearDown(); + } + } +} diff --git a/Unity.Entities.Hybrid.Tests/ComponentGroupTransformAccessArrayTests.cs.meta b/Unity.Entities.Hybrid.Tests/ComponentGroupTransformAccessArrayTests.cs.meta new file mode 100644 index 00000000..719dbb5b --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/ComponentGroupTransformAccessArrayTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 920744fb267a2406b90d6a988379cc79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.Tests/GameObjectEntityTests.cs b/Unity.Entities.Hybrid.Tests/GameObjectEntityTests.cs new file mode 100644 index 00000000..2151ce17 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/GameObjectEntityTests.cs @@ -0,0 +1,101 @@ +using NUnit.Framework; +using Unity.Entities; +using Unity.Entities.Tests; + +namespace UnityEngine.Entities.Tests +{ + public class GameObjectEntityTests : ECSTestsFixture + { + ComponentArrayInjectionHook m_ComponentArrayInjectionHook = new ComponentArrayInjectionHook(); + GameObjectArrayInjectionHook m_GameObjectArrayInjectionHook = new GameObjectArrayInjectionHook(); + + [OneTimeSetUp] + public void Init() + { + InjectionHookSupport.RegisterHook(m_ComponentArrayInjectionHook); + InjectionHookSupport.RegisterHook(m_GameObjectArrayInjectionHook); + } + + [OneTimeTearDown] + public void Cleanup() + { + InjectionHookSupport.UnregisterHook(m_GameObjectArrayInjectionHook); + InjectionHookSupport.RegisterHook(m_ComponentArrayInjectionHook); + } + + [DisableAutoCreation] + public class GameObjectArraySystem : ComponentSystem + { + public struct Group + { + public int Length; + public GameObjectArray gameObjects; + + public ComponentArray colliders; + } + + [Inject] + public Group group; + + protected override void OnUpdate() + { + } + + public new void UpdateInjectedComponentGroups() + { + base.UpdateInjectedComponentGroups(); + } + } + + [Test] + public void GameObjectArrayIsPopulated() + { + var go = new GameObject("test", typeof(BoxCollider)); + GameObjectEntity.AddToEntityManager(m_Manager, go); + + var manager = World.GetOrCreateManager(); + + manager.UpdateInjectedComponentGroups(); + + Assert.AreEqual(1, manager.group.Length); + Assert.AreEqual(go, manager.group.gameObjects[0]); + Assert.AreEqual(go, manager.group.colliders[0].gameObject); + + Object.DestroyImmediate (go); + TearDown(); + } + + [Test] + public void ComponentDataAndTransformArray() + { + var go = new GameObject("test", typeof(EcsTestComponent)); + var entity = GameObjectEntity.AddToEntityManager(m_Manager, go); + + m_Manager.SetComponentData(entity, new EcsTestData(5)); + + var grp = EmptySystem.GetComponentGroup(typeof(Transform), typeof(EcsTestData)); + var arr = grp.GetComponentArray(); + + Assert.AreEqual(1, arr.Length); + Assert.AreEqual(go.transform, arr[0]); + Assert.AreEqual(5, grp.GetComponentDataArray()[0].value); + + Object.DestroyImmediate (go); + } + + [Test] + public void RigidbodyComponentArray() + { + var go = new GameObject("test", typeof(Rigidbody)); + /*var entity =*/ GameObjectEntity.AddToEntityManager(m_Manager, go); + + var grp = EmptySystem.GetComponentGroup(typeof(Rigidbody)); + + var arr = grp.GetComponentArray(); + Assert.AreEqual(1, arr.Length); + Assert.AreEqual(go.GetComponent(), arr[0]); + + Object.DestroyImmediate(go); + } + } +} diff --git a/Unity.Entities.Hybrid.Tests/GameObjectEntityTests.cs.meta b/Unity.Entities.Hybrid.Tests/GameObjectEntityTests.cs.meta new file mode 100644 index 00000000..12e01d57 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/GameObjectEntityTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96715d53145034e32b91901f366545b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.Tests/Runtime.meta b/Unity.Entities.Hybrid.Tests/Runtime.meta new file mode 100644 index 00000000..86544380 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 177784199dc2f414e8c27dd1602dc3ba +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.Tests/Runtime/EntityManagerTests.cs b/Unity.Entities.Hybrid.Tests/Runtime/EntityManagerTests.cs new file mode 100644 index 00000000..12c9a89d --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Runtime/EntityManagerTests.cs @@ -0,0 +1,38 @@ +using NUnit.Framework; +using UnityEngine; +using Unity.Entities.Tests; + +namespace Unity.Entities.Tests +{ + public class EcsFooTestComponent : ComponentDataWrapper { } + public class EcsTestComponent : ComponentDataWrapper { } + + public class EntityManagerTests : ECSTestsFixture + { + [Test] + public void GetComponentObjectReturnsTheCorrectType() + { + var go = new GameObject(); + go.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnEnable(); + + var component = m_Manager.GetComponentObject(go.GetComponent().Entity); + + Assert.NotNull(component, "EntityManager.GetComponentObject returned a null object"); + Assert.AreEqual(typeof(Transform), component.GetType(), "EntityManager.GetComponentObject returned the wrong component type."); + Assert.AreEqual(go.transform, component, "EntityManager.GetComponentObject returned a different copy of the component."); + } + + [Test] + public void GetComponentObjectThrowsIfComponentDoesNotExist() + { + var go = new GameObject(); + go.AddComponent(); + // Execute in edit mode is not enabled so this has to be called manually right now + go.GetComponent().OnEnable(); + + Assert.Throws(() => m_Manager.GetComponentObject(go.GetComponent().Entity)); + } + } +} diff --git a/Unity.Entities.Hybrid.Tests/Runtime/EntityManagerTests.cs.meta b/Unity.Entities.Hybrid.Tests/Runtime/EntityManagerTests.cs.meta new file mode 100644 index 00000000..1469f57f --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Runtime/EntityManagerTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5ef0543d8deef4fadacecfdc5802d552 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.Tests/Runtime/GameObjectEntityTests.cs b/Unity.Entities.Hybrid.Tests/Runtime/GameObjectEntityTests.cs new file mode 100644 index 00000000..0f1f52c1 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Runtime/GameObjectEntityTests.cs @@ -0,0 +1,77 @@ +using System; +using NUnit.Framework; +using Unity.Entities; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Unity.Entities.Tests +{ + //@TODO: Test for prevent adding Wrapper component to type system... + + public class GameObjectEntityTests : ECSTestsFixture + { + [Test] + [Ignore("not implemented")] + public void ComponentArrayWithParentClass() { } + + + [Test] + public void TransformAccessArrayTests() + { + + } + + [Test] + public void GameObjectEntityNotAdded() + { + var go = new GameObject("test", typeof(GameObjectEntity)); + var entity = GameObjectEntity.AddToEntityManager(m_Manager, go); + Assert.Throws(() => { m_Manager.HasComponent(entity); }); + } + + unsafe struct MyEntity + { + public Light light; + public Rigidbody rigidbody; + + public EcsTestData* testData; + public EcsTestData2* testData2; + } + + [Test] + [Ignore("TODO")] + public void ComponentEnumeratorInvalidChecks() + { + //* Check for string in MyEntity and other illegal constructs... + } + + [Test] + [Ignore("TODO")] + public void AddComponentDuringForeachProtection() + { + //* Check for string in MyEntity and other illegal constructs... + } + [Test] + unsafe public void ComponentEnumerator() + { + var go = new GameObject("test", typeof(Rigidbody), typeof(Light)); + var entity = GameObjectEntity.AddToEntityManager(m_Manager, go); + + m_Manager.AddComponentData(entity, new EcsTestData(5)); + m_Manager.AddComponentData(entity, new EcsTestData2(6)); + + int iterations = 0; + foreach (var e in EmptySystem.GetEntities() ) + { + Assert.AreEqual(5, e.testData->value); + Assert.AreEqual(6, e.testData2->value0); + Assert.AreEqual(go.GetComponent(), e.light); + Assert.AreEqual(go.GetComponent(), e.rigidbody); + iterations++; + } + Assert.AreEqual(1, iterations); + + Object.DestroyImmediate(go); + } + } +} diff --git a/Unity.Entities.Hybrid.Tests/Runtime/GameObjectEntityTests.cs.meta b/Unity.Entities.Hybrid.Tests/Runtime/GameObjectEntityTests.cs.meta new file mode 100644 index 00000000..44400d06 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Runtime/GameObjectEntityTests.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 40b5b40695ca142fb8ff7bd50170597b +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.Tests/Runtime/InjectComponentGroupTestsHybrid.cs b/Unity.Entities.Hybrid.Tests/Runtime/InjectComponentGroupTestsHybrid.cs new file mode 100644 index 00000000..cfa2c854 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Runtime/InjectComponentGroupTestsHybrid.cs @@ -0,0 +1,57 @@ +using NUnit.Framework; +using UnityEngine; +using Unity.Entities.Tests; + +namespace Unity.Entities.Tests +{ + public class InjectComponentGroupTestsHybrid : ECSTestsFixture + { + [DisableAutoCreation] + [AlwaysUpdateSystem] + public class SubtractiveSystem : ComponentSystem + { + public struct Datas + { + public ComponentDataArray Data; + public SubtractiveComponent Data2; + public SubtractiveComponent Rigidbody; + } + + [Inject] + public Datas Group; + + protected override void OnUpdate() + { + } + } + + [Test] + public void SubtractiveComponent() + { + var subtractiveSystem = World.GetOrCreateManager (); + + var entity = m_Manager.CreateEntity (typeof(EcsTestData)); + + var go = new GameObject("Test", typeof(EcsTestComponent)); + go.GetComponent().OnEnable(); + + // Ensure entities without the subtractive components are present + subtractiveSystem.Update (); + Assert.AreEqual (2, subtractiveSystem.Group.Data.Length); + Assert.AreEqual (0, subtractiveSystem.Group.Data[0].value); + Assert.AreEqual (0, subtractiveSystem.Group.Data[1].value); + + // Ensure adding the subtractive components, removes them from the injection + m_Manager.AddComponentData (entity, new EcsTestData2()); + + // TODO: This should be automatic... + go.AddComponent(); + go.GetComponent().OnDisable(); go.GetComponent().OnEnable(); + + subtractiveSystem.Update (); + Assert.AreEqual (0, subtractiveSystem.Group.Data.Length); + + Object.DestroyImmediate(go); + } + } +} diff --git a/Unity.Entities.Hybrid.Tests/Runtime/InjectComponentGroupTestsHybrid.cs.meta b/Unity.Entities.Hybrid.Tests/Runtime/InjectComponentGroupTestsHybrid.cs.meta new file mode 100644 index 00000000..e9f18fe8 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Runtime/InjectComponentGroupTestsHybrid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 241a4da0c00ab408d84c63e4dd20a864 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.Tests/Unity.Entities.Hybrid.Tests.asmdef b/Unity.Entities.Hybrid.Tests/Unity.Entities.Hybrid.Tests.asmdef new file mode 100644 index 00000000..869777f0 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Unity.Entities.Hybrid.Tests.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Unity.Entities.Hybrid.Tests", + "references": [ + "Unity.Entities", + "Unity.Entities.Hybrid", + "Unity.Entities.Tests", + "Unity.Collections", + "Unity.Jobs" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true +} diff --git a/Unity.Entities.Hybrid.Tests/Unity.Entities.Hybrid.Tests.asmdef.meta b/Unity.Entities.Hybrid.Tests/Unity.Entities.Hybrid.Tests.asmdef.meta new file mode 100644 index 00000000..e1ee4928 --- /dev/null +++ b/Unity.Entities.Hybrid.Tests/Unity.Entities.Hybrid.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: bd9484a338da1458bb1d27afe56e6f90 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid.meta b/Unity.Entities.Hybrid.meta new file mode 100644 index 00000000..8b05ba17 --- /dev/null +++ b/Unity.Entities.Hybrid.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 78757c525e72c431ca28f37d9173a932 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/AssemblyInfo.cs b/Unity.Entities.Hybrid/AssemblyInfo.cs new file mode 100644 index 00000000..e5597d8e --- /dev/null +++ b/Unity.Entities.Hybrid/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Unity.Entities.Hybrid.Tests")] diff --git a/Unity.Entities.Hybrid/AssemblyInfo.cs.meta b/Unity.Entities.Hybrid/AssemblyInfo.cs.meta new file mode 100644 index 00000000..e4523f94 --- /dev/null +++ b/Unity.Entities.Hybrid/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f99c4766b3e2464c978e3a1ebeb94c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/EntityManagerExtensions.cs b/Unity.Entities.Hybrid/EntityManagerExtensions.cs new file mode 100644 index 00000000..05351a3e --- /dev/null +++ b/Unity.Entities.Hybrid/EntityManagerExtensions.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; + +namespace Unity.Entities +{ + public static class EntityManagerExtensions + { + unsafe public static Entity Instantiate(this EntityManager entityManager, GameObject srcGameObject) + { + if (entityManager.m_CachedComponentList == null) + entityManager.m_CachedComponentList = new List(); + + var components = (List)entityManager.m_CachedComponentList; + srcGameObject.GetComponents(components); + var count = components.Count; + ComponentType* componentTypes = stackalloc ComponentType[count]; + + for (var t = 0; t != count; ++t) + componentTypes[t] = components[t].GetComponentType(); + + var srcEntity = entityManager.CreateEntity(entityManager.CreateArchetype(componentTypes, count)); + for (var t = 0; t != count; ++t) + components[t].UpdateComponentData(entityManager, srcEntity); + + return srcEntity; + } + + public static unsafe void Instantiate(this EntityManager entityManager, GameObject srcGameObject, NativeArray outputEntities) + { + if (outputEntities.Length == 0) + return; + + var entity = entityManager.Instantiate(srcGameObject); + outputEntities[0] = entity; + + var entityPtr = (Entity*)outputEntities.GetUnsafePtr(); + entityManager.InstantiateInternal(entity, entityPtr + 1, outputEntities.Length - 1); + } + + public static unsafe T GetComponentObject(this EntityManager entityManager, Entity entity) where T : Component + { + var componentType = ComponentType.Create(); + entityManager.Entities->AssertEntityHasComponent(entity, componentType.TypeIndex); + + Chunk* chunk; + int chunkIndex; + entityManager.Entities->GetComponentChunk(entity, out chunk, out chunkIndex); + return entityManager.ArchetypeManager.GetManagedObject(chunk, componentType, chunkIndex) as T; + } + } +} diff --git a/Unity.Entities.Hybrid/EntityManagerExtensions.cs.meta b/Unity.Entities.Hybrid/EntityManagerExtensions.cs.meta new file mode 100644 index 00000000..ff0f6072 --- /dev/null +++ b/Unity.Entities.Hybrid/EntityManagerExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84985f39d7cdc405da65cd6bb6c6dc6b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/GameObjectEntity.cs b/Unity.Entities.Hybrid/GameObjectEntity.cs new file mode 100644 index 00000000..cf153ac8 --- /dev/null +++ b/Unity.Entities.Hybrid/GameObjectEntity.cs @@ -0,0 +1,249 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using RequireComponent = UnityEngine.RequireComponent; +using SerializeField = UnityEngine.SerializeField; +using MonoBehaviour = UnityEngine.MonoBehaviour; +using DisallowMultipleComponent = UnityEngine.DisallowMultipleComponent; +using GameObject = UnityEngine.GameObject; +using Component = UnityEngine.Component; + +namespace Unity.Entities +{ + //@TODO: This should be fully implemented in C++ for efficiency + [RequireComponent(typeof(GameObjectEntity))] + public abstract class ComponentDataWrapperBase : MonoBehaviour, ISerializationCallbackReceiver + { + internal abstract ComponentType GetComponentType(); + internal abstract void UpdateComponentData(EntityManager manager, Entity entity); + internal abstract void UpdateSerializedData(EntityManager manager, Entity entity); + + void OnValidate() + { + var gameObjectEntity = GetComponent(); + if (gameObjectEntity == null) + return; + if (gameObjectEntity.EntityManager == null) + return; + if (!gameObjectEntity.EntityManager.Exists(gameObjectEntity.Entity)) + return; + if (!gameObjectEntity.EntityManager.HasComponent(gameObjectEntity.Entity, GetComponentType())) + return; + + UpdateComponentData(gameObjectEntity.EntityManager, gameObjectEntity.Entity); + } + + public void OnBeforeSerialize() + { + var gameObjectEntity = GetComponent(); + if (gameObjectEntity == null) + return; + if (gameObjectEntity.EntityManager == null) + return; + if (!gameObjectEntity.EntityManager.Exists(gameObjectEntity.Entity)) + return; + if (!gameObjectEntity.EntityManager.HasComponent(gameObjectEntity.Entity, GetComponentType())) + return; + UpdateSerializedData(gameObjectEntity.EntityManager, gameObjectEntity.Entity); + } + + public void OnAfterDeserialize() { } + } + + //@TODO: This should be fully implemented in C++ for efficiency + public class ComponentDataWrapper : ComponentDataWrapperBase where T : struct, IComponentData + { + [SerializeField] + T m_SerializedData; + + public T Value + { + get + { + return m_SerializedData; + } + set + { + m_SerializedData = value; + } + } + + + internal override ComponentType GetComponentType() + { + return ComponentType.Create(); + } + + internal override void UpdateComponentData(EntityManager manager, Entity entity) + { + manager.SetComponentData(entity, m_SerializedData); + } + + internal override void UpdateSerializedData(EntityManager manager, Entity entity) + { + m_SerializedData = manager.GetComponentData(entity); + } + } + + //@TODO: This should be fully implemented in C++ for efficiency + public class SharedComponentDataWrapper : ComponentDataWrapperBase where T : struct, ISharedComponentData + { + [SerializeField] + T m_SerializedData; + + public T Value + { + get + { + return m_SerializedData; + } + set + { + m_SerializedData = value; + } + } + + + internal override ComponentType GetComponentType() + { + return ComponentType.Create(); + } + + internal override void UpdateComponentData(EntityManager manager, Entity entity) + { + manager.SetSharedComponentData(entity, m_SerializedData); + } + + internal override void UpdateSerializedData(EntityManager manager, Entity entity) + { + m_SerializedData = manager.GetSharedComponentData(entity); + } + } + + [DisallowMultipleComponent] + [ExecuteInEditMode] + public class GameObjectEntity : MonoBehaviour + { + public EntityManager EntityManager { get; private set; } + + public Entity Entity { get; private set; } + + //@TODO: Very wrong error messages when creating entity with empty ComponentType array? + + public static Entity AddToEntityManager(EntityManager entityManager, GameObject gameObject) + { + ComponentType[] types; + Component[] components; + GetComponents(gameObject, true, out types, out components); + + var archetype = entityManager.CreateArchetype(types); + var entity = CreateEntity(entityManager, archetype, components, types); + + return entity; + } + + static void GetComponents(GameObject gameObject, bool includeGameObjectComponents, out ComponentType[] types, out Component[] components) + { + components = gameObject.GetComponents(); + + var componentCount = 0; + if (includeGameObjectComponents) + { + var gameObjectEntityComponent = gameObject.GetComponent(); + componentCount = gameObjectEntityComponent == null ? components.Length : components.Length - 1; + } + else + { + for (var i = 0; i != components.Length; i++) + { + if (components[i] is ComponentDataWrapperBase) + componentCount++; + } + } + + types = new ComponentType[componentCount]; + + var t = 0; + for (var i = 0; i != components.Length; i++) + { + var com = components[i]; + var componentData = com as ComponentDataWrapperBase; + + if (componentData != null) + types[t++] = componentData.GetComponentType(); + else if (includeGameObjectComponents && !(com is GameObjectEntity)) + types[t++] = com.GetType(); + } + } + + static Entity CreateEntity(EntityManager entityManager, EntityArchetype archetype, IReadOnlyList components, IReadOnlyList types) + { + var entity = entityManager.CreateEntity(archetype); + var t = 0; + for (var i = 0; i != components.Count; i++) + { + var com = components[i]; + var componentDataWrapper = com as ComponentDataWrapperBase; + + if (componentDataWrapper != null) + { + componentDataWrapper.UpdateComponentData(entityManager, entity); + t++; + } + else if (!(com is GameObjectEntity)) + { + entityManager.SetComponentObject(entity, types[t], com); + t++; + } + } + return entity; + } + + public void OnEnable() + { + #if UNITY_EDITOR + if (World.Active == null) + { + // * OnDisable (Serialize monobehaviours in temporary backup) + // * unload domain + // * load new domain + // * OnEnable (Deserialize monobehaviours in temporary backup) + // * mark entered playmode / load scene + // * OnDisable / OnDestroy + // * OnEnable (Loading object from scene...) + if (EditorApplication.isPlayingOrWillChangePlaymode) + { + // We are just gonna ignore this enter playmode reload. + // Can't see a situation where it would be useful to create something inbetween. + // But we really need to solve this at the root. The execution order is kind if crazy. + if (!EditorApplication.isPlaying) + return; + + Debug.LogError("Loading GameObjectEntity in Playmode but there is no active World"); + return; + } + else + { +#if UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP + return; +#else + DefaultWorldInitialization.Initialize("Editor World", true); +#endif + } + } + #endif + + EntityManager = World.Active.GetOrCreateManager(); + Entity = AddToEntityManager(EntityManager, gameObject); + } + + public void OnDisable() + { + if (EntityManager != null && EntityManager.IsCreated && EntityManager.Exists(Entity)) + EntityManager.DestroyEntity(Entity); + + EntityManager = null; + Entity = new Entity(); + } + } +} diff --git a/Unity.Entities.Hybrid/GameObjectEntity.cs.meta b/Unity.Entities.Hybrid/GameObjectEntity.cs.meta new file mode 100644 index 00000000..f6dc09fa --- /dev/null +++ b/Unity.Entities.Hybrid/GameObjectEntity.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 5bf10cdea1344482e91a4f2b58506b77 +timeCreated: 1505389344 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: -16960 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/Injection.meta b/Unity.Entities.Hybrid/Injection.meta new file mode 100644 index 00000000..aee3ae98 --- /dev/null +++ b/Unity.Entities.Hybrid/Injection.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f82ba39c4ae7b431fa30dcbccf259f9d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs b/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs new file mode 100644 index 00000000..f3d94df5 --- /dev/null +++ b/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +namespace Unity.Entities +{ +#if !UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP + static class AutomaticWorldBootstrap + { + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + static void Initialize() + { + DefaultWorldInitialization.Initialize("Default World", false); + } + } +#endif +} diff --git a/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs.meta b/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs.meta new file mode 100644 index 00000000..cfed778e --- /dev/null +++ b/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a154d14c142fd4105bf35acdba86d350 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs b/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs new file mode 100644 index 00000000..244bf47d --- /dev/null +++ b/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace Unity.Entities +{ + static class DefaultWorldInitialization + { + static void DomainUnloadShutdown() + { + World.DisposeAllWorlds(); + ScriptBehaviourUpdateOrder.UpdatePlayerLoop(); + } + + static void GetBehaviourManagerAndLogException(World world, Type type) + { + try + { + world.GetOrCreateManager(type); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + + public static void Initialize(string worldName, bool editorWorld) + { + var world = new World(worldName); + World.Active = world; + + // Register hybrid injection hooks + InjectionHookSupport.RegisterHook(new GameObjectArrayInjectionHook()); + InjectionHookSupport.RegisterHook(new TransformAccessArrayInjectionHook()); + InjectionHookSupport.RegisterHook(new ComponentArrayInjectionHook()); + + PlayerLoopManager.RegisterDomainUnload(DomainUnloadShutdown, 10000); + + foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + var allTypes = ass.GetTypes(); + + // Create all ComponentSyste + var systemTypes = allTypes.Where(t => + t.IsSubclassOf(typeof(ComponentSystemBase)) && + !t.IsAbstract && + !t.ContainsGenericParameters && + (t.GetCustomAttributes(typeof(ComponentSystemPatchAttribute), true).Length == 0) && + t.GetCustomAttributes(typeof(DisableAutoCreationAttribute), true).Length == 0); + foreach (var type in systemTypes) + { + if (editorWorld && type.GetCustomAttributes(typeof(ExecuteInEditMode), true).Length == 0) + continue; + + GetBehaviourManagerAndLogException(world, type); + } + } + catch (ReflectionTypeLoadException) + { + // Can happen for certain assembly during the GetTypes() step + } + } + + foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) + { + var allTypes = ass.GetTypes(); + + // Create all ComponentSystem + var systemTypes = allTypes.Where(t => + t.IsSubclassOf(typeof(ComponentSystemBase)) && + !t.IsAbstract && + !t.ContainsGenericParameters && + (t.GetCustomAttributes(typeof(ComponentSystemPatchAttribute), true).Length > 0) && + t.GetCustomAttributes(typeof(DisableAutoCreationAttribute), true).Length == 0); + foreach (var type in systemTypes) + { + if (editorWorld && type.GetCustomAttributes(typeof(ExecuteInEditMode), true).Length == 0) + continue; + + world.AddComponentSystemPatch(type); + } + } + + ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world); + } + } +} diff --git a/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs.meta b/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs.meta new file mode 100644 index 00000000..0eec5094 --- /dev/null +++ b/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef7ced22b40024dc9842af0b8ea0de42 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/Iterators.meta b/Unity.Entities.Hybrid/Iterators.meta new file mode 100644 index 00000000..17602ce4 --- /dev/null +++ b/Unity.Entities.Hybrid/Iterators.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e72e4b442466f429db62ca098867d5b2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/Iterators/ComponentArray.cs b/Unity.Entities.Hybrid/Iterators/ComponentArray.cs new file mode 100644 index 00000000..d1605d90 --- /dev/null +++ b/Unity.Entities.Hybrid/Iterators/ComponentArray.cs @@ -0,0 +1,134 @@ +using System; +using System.Reflection; + +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.Scripting; + +namespace Unity.Entities +{ + public static class ComponentGroupExtensionsForComponentArray + { + public static ComponentArray GetComponentArray(this ComponentGroup group) where T : Component + { + int length; + ComponentChunkIterator iterator; + group.GetComponentChunkIterator(out length, out iterator); + var indexInComponentGroup = group.GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + + iterator.IndexInComponentGroup = indexInComponentGroup; + return new ComponentArray(iterator, length, group.ArchetypeManager); + } + } +} + +namespace Unity.Entities +{ + public struct ComponentArray where T: Component + { + ComponentChunkIterator m_Iterator; + ComponentChunkCache m_Cache; + readonly int m_Length; + readonly ArchetypeManager m_ArchetypeManager; + + internal ComponentArray(ComponentChunkIterator iterator, int length, ArchetypeManager typeMan) + { + m_Length = length; + m_Cache = default(ComponentChunkCache); + m_Iterator = iterator; + m_ArchetypeManager = typeMan; + } + + public T this[int index] + { + get + { + //@TODO: Unnecessary.. integrate into cache instead... + if ((uint)index >= (uint)m_Length) + FailOutOfRangeError(index); + + if (index < m_Cache.CachedBeginIndex || index >= m_Cache.CachedEndIndex) + m_Iterator.UpdateCache(index, out m_Cache, true); + + return (T)m_Iterator.GetManagedObject(m_ArchetypeManager, m_Cache.CachedBeginIndex, index); + } + } + + public T[] ToArray() + { + var arr = new T[m_Length]; + var i = 0; + while (i < m_Length) + { + m_Iterator.UpdateCache(i, out m_Cache, true); + int start, length; + var objs = m_Iterator.GetManagedObjectRange(m_ArchetypeManager, m_Cache.CachedBeginIndex, i, out start, out length); + for (var obj = 0; obj < length; ++obj) + arr[i+obj] = (T)objs[start+obj]; + i += length; + } + return arr; + } + + void FailOutOfRangeError(int index) + { + throw new IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length."); + } + + public int Length => m_Length; + } + + [Preserve] + [CustomInjectionHook] + sealed class ComponentArrayInjectionHook : InjectionHook + { + public override Type FieldTypeOfInterest => typeof(ComponentArray<>); + + public override bool IsInterestedInField(FieldInfo fieldInfo) + { + return fieldInfo.FieldType.IsGenericType && fieldInfo.FieldType.GetGenericTypeDefinition() == typeof(ComponentArray<>); + } + + public override string ValidateField(FieldInfo field, bool isReadOnly, InjectionContext injectionInfo) + { + if (field.FieldType != typeof(ComponentArray<>)) + return null; + + if (isReadOnly) + return "[ReadOnly] may not be used on ComponentArray<>, it can only be used on ComponentDataArray<>"; + + return null; + } + + public override InjectionContext.Entry CreateInjectionInfoFor(FieldInfo field, bool isReadOnly) + { + var componentType = field.FieldType.GetGenericArguments()[0]; + var accessMode = isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite; + return new InjectionContext.Entry + { + Hook = this, + FieldInfo = field, + IsReadOnly = false /* isReadOnly */, + AccessMode = accessMode, + IndexInComponentGroup = -1, + FieldOffset = UnsafeUtility.GetFieldOffset(field), + ComponentType = new ComponentType(componentType, accessMode), + ComponentRequirements = componentType == typeof(Transform) + ? new[] { typeof(Transform), componentType } + : new []{ componentType } + }; + } + + public override void PrepareEntry(ref InjectionContext.Entry entry, ComponentGroup entityGroup) + { + entry.IndexInComponentGroup = entityGroup.GetIndexInComponentGroup(entry.ComponentType.TypeIndex); + } + + internal override unsafe void InjectEntry(InjectionContext.Entry entry, ComponentGroup entityGroup, ref ComponentChunkIterator iterator, int length, byte* groupStructPtr) + { + iterator.IndexInComponentGroup = entry.IndexInComponentGroup; + var data = new ComponentArray(iterator, length, entityGroup.ArchetypeManager); + UnsafeUtility.CopyStructureToPtr(ref data, groupStructPtr + entry.FieldOffset); + } + } +} diff --git a/Unity.Entities.Hybrid/Iterators/ComponentArray.cs.meta b/Unity.Entities.Hybrid/Iterators/ComponentArray.cs.meta new file mode 100644 index 00000000..85bbab81 --- /dev/null +++ b/Unity.Entities.Hybrid/Iterators/ComponentArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 34268c576208d43dbad663375a17f735 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/Iterators/GameObjectArray.cs b/Unity.Entities.Hybrid/Iterators/GameObjectArray.cs new file mode 100644 index 00000000..8321f6e1 --- /dev/null +++ b/Unity.Entities.Hybrid/Iterators/GameObjectArray.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using System.Reflection; + +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.Scripting; + +namespace Unity.Entities +{ + public static class ComponentGroupExtensionsForGameObjectArray + { + public static GameObjectArray GetGameObjectArray(this ComponentGroup group) + { + int length; + ComponentChunkIterator iterator; + group.GetComponentChunkIterator(out length, out iterator); + iterator.IndexInComponentGroup = group.GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + return new GameObjectArray(iterator, length, group.ArchetypeManager); + } + } +} + +namespace Unity.Entities +{ + public struct GameObjectArray + { + ComponentChunkIterator m_Iterator; + ComponentChunkCache m_Cache; + readonly int m_Length; + readonly ArchetypeManager m_ArchetypeManager; + + internal GameObjectArray(ComponentChunkIterator iterator, int length, ArchetypeManager typeMan) + { + m_Length = length; + m_Cache = default(ComponentChunkCache); + m_Iterator = iterator; + m_ArchetypeManager = typeMan; + } + + public GameObject this[int index] + { + get + { + //@TODO: Unnecessary.. integrate into cache instead... + if ((uint)index >= (uint)m_Length) + FailOutOfRangeError(index); + + if (index < m_Cache.CachedBeginIndex || index >= m_Cache.CachedEndIndex) + m_Iterator.UpdateCache(index, out m_Cache, true); + + var transform = (Transform) m_Iterator.GetManagedObject(m_ArchetypeManager, m_Cache.CachedBeginIndex, index); + + return transform.gameObject; + } + } + + public GameObject[] ToArray() + { + var arr = new GameObject[m_Length]; + var i = 0; + while (i < m_Length) + { + m_Iterator.UpdateCache(i, out m_Cache, true); + int start, length; + var objs = m_Iterator.GetManagedObjectRange(m_ArchetypeManager, m_Cache.CachedBeginIndex, i, out start, out length); + for (var obj = 0; obj < length; ++obj) + arr[i+obj] = ((Transform) objs[start + obj]).gameObject; + i += length; + } + return arr; + } + + void FailOutOfRangeError(int index) + { + throw new IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length."); + } + + public int Length => m_Length; + } + + [Preserve] + [CustomInjectionHook] + sealed class GameObjectArrayInjectionHook : InjectionHook + { + public override Type FieldTypeOfInterest => typeof(GameObjectArray); + + public override bool IsInterestedInField(FieldInfo fieldInfo) + { + return fieldInfo.FieldType == typeof(GameObjectArray); + } + + public override string ValidateField(FieldInfo field, bool isReadOnly, InjectionContext injectionInfo) + { + if (field.FieldType != typeof(GameObjectArray)) + return null; + + if (isReadOnly) + return "[ReadOnly] may not be used on GameObjectArray, it can only be used on ComponentDataArray<>"; + + // Error on multiple GameObjectArray + if (injectionInfo.Entries.Any(i => i.FieldInfo.FieldType == typeof(GameObjectArray))) + return "A [Inject] struct, may only contain a single GameObjectArray"; + + return null; + } + + public override InjectionContext.Entry CreateInjectionInfoFor(FieldInfo field, bool isReadOnly) + { + return new InjectionContext.Entry + { + Hook = this, + FieldInfo = field, + IsReadOnly = isReadOnly, + AccessMode = isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite, + IndexInComponentGroup = -1, + FieldOffset = UnsafeUtility.GetFieldOffset(field), + ComponentRequirements = new[] { typeof(Transform) } + }; + } + + internal override unsafe void InjectEntry(InjectionContext.Entry entry, ComponentGroup entityGroup, ref ComponentChunkIterator iterator, int length, byte* groupStructPtr) + { + var gameObjectArray = entityGroup.GetGameObjectArray(); + UnsafeUtility.CopyStructureToPtr(ref gameObjectArray, groupStructPtr + entry.FieldOffset); + } + } +} diff --git a/Unity.Entities.Hybrid/Iterators/GameObjectArray.cs.meta b/Unity.Entities.Hybrid/Iterators/GameObjectArray.cs.meta new file mode 100644 index 00000000..3fca4764 --- /dev/null +++ b/Unity.Entities.Hybrid/Iterators/GameObjectArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5558e38bbc5c843679c87e532e532994 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/Iterators/TransformAccessArrayIterator.cs b/Unity.Entities.Hybrid/Iterators/TransformAccessArrayIterator.cs new file mode 100644 index 00000000..3d1575a4 --- /dev/null +++ b/Unity.Entities.Hybrid/Iterators/TransformAccessArrayIterator.cs @@ -0,0 +1,96 @@ +using System; +using System.Linq; +using System.Reflection; + +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.Jobs; +using UnityEngine.Scripting; + +namespace Unity.Entities +{ + struct TransformAccessArrayState : IDisposable + { + public TransformAccessArray Data; + public int OrderVersion; + + public void Dispose() + { + if (Data.isCreated) + Data.Dispose(); + } + } + + public static class ComponentGroupExtensionsForTransformAccessArray + { + public static unsafe TransformAccessArray GetTransformAccessArray(this ComponentGroup group) + { + var state = (TransformAccessArrayState?)group.m_CachedState ?? new TransformAccessArrayState(); + var orderVersion = group.EntityDataManager->GetComponentTypeOrderVersion(TypeManager.GetTypeIndex()); + + if (state.Data.isCreated && orderVersion == state.OrderVersion) + return state.Data; + + state.OrderVersion = orderVersion; + + UnityEngine.Profiling.Profiler.BeginSample("DirtyTransformAccessArrayUpdate"); + var trans = group.GetComponentArray(); + if (!state.Data.isCreated) + state.Data = new TransformAccessArray(trans.ToArray()); + else + state.Data.SetTransforms(trans.ToArray()); + UnityEngine.Profiling.Profiler.EndSample(); + + group.m_CachedState = state; + + return state.Data; + } + } +} + +namespace Unity.Entities +{ + [Preserve] + [CustomInjectionHook] + sealed class TransformAccessArrayInjectionHook : InjectionHook + { + public override Type FieldTypeOfInterest => typeof(TransformAccessArray); + + public override bool IsInterestedInField(FieldInfo fieldInfo) + { + return fieldInfo.FieldType == typeof(TransformAccessArray); + } + + public override string ValidateField(FieldInfo field, bool isReadOnly, InjectionContext injectionInfo) + { + if (isReadOnly) + return "[ReadOnly] may not be used on a TransformAccessArray only on ComponentDataArray<>"; + + // Error on multiple TransformAccessArray + if (injectionInfo.Entries.Any(i => i.FieldInfo.FieldType == typeof(TransformAccessArray))) + return "A [Inject] struct, may only contain a single TransformAccessArray"; + + return null; + } + + public override InjectionContext.Entry CreateInjectionInfoFor(FieldInfo field, bool isReadOnly) + { + return new InjectionContext.Entry + { + Hook = this, + FieldInfo = field, + IsReadOnly = isReadOnly, + AccessMode = isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite, + IndexInComponentGroup = -1, + FieldOffset = UnsafeUtility.GetFieldOffset(field), + ComponentRequirements = new[] { typeof(Transform) } + }; + } + + internal override unsafe void InjectEntry(InjectionContext.Entry entry, ComponentGroup entityGroup, ref ComponentChunkIterator iterator, int length, byte* groupStructPtr) + { + var transformsArray = entityGroup.GetTransformAccessArray(); + UnsafeUtility.CopyStructureToPtr(ref transformsArray, groupStructPtr + entry.FieldOffset); + } + } +} diff --git a/Unity.Entities.Hybrid/Iterators/TransformAccessArrayIterator.cs.meta b/Unity.Entities.Hybrid/Iterators/TransformAccessArrayIterator.cs.meta new file mode 100644 index 00000000..94dc25a9 --- /dev/null +++ b/Unity.Entities.Hybrid/Iterators/TransformAccessArrayIterator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a07ee693bbb9483f9e950399cc6064c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/PlayerLoopDisableManager.cs b/Unity.Entities.Hybrid/PlayerLoopDisableManager.cs new file mode 100644 index 00000000..68e95be1 --- /dev/null +++ b/Unity.Entities.Hybrid/PlayerLoopDisableManager.cs @@ -0,0 +1,25 @@ +using UnityEngine; + +namespace Unity.Entities +{ + [ExecuteInEditMode] + class PlayerLoopDisableManager : MonoBehaviour + { + public bool IsActive; + + public void OnEnable() + { + if (!IsActive) + return; + + IsActive = false; + DestroyImmediate(gameObject); + } + + public void OnDisable() + { + if (IsActive) + PlayerLoopManager.InvokeBeforeDomainUnload(); + } + } +} diff --git a/Unity.Entities.Hybrid/PlayerLoopDisableManager.cs.meta b/Unity.Entities.Hybrid/PlayerLoopDisableManager.cs.meta new file mode 100644 index 00000000..d32d11c8 --- /dev/null +++ b/Unity.Entities.Hybrid/PlayerLoopDisableManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7513b7bb884e4956b60076192d1276b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/PlayerLoopManager.cs b/Unity.Entities.Hybrid/PlayerLoopManager.cs new file mode 100644 index 00000000..519c8e75 --- /dev/null +++ b/Unity.Entities.Hybrid/PlayerLoopManager.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.Entities +{ + public static class PlayerLoopManager + { + struct UnloadMethod : IComparable + { + public CallbackFunction Function; + public int Ordering; + + public int CompareTo(UnloadMethod other) + { + return Ordering - other.Ordering; + } + } + + static List k_DomainUnloadMethods; + + public delegate void CallbackFunction(); + + /// + /// Register a function to be called when the scripting domain is unloading. + /// + /// The function to call + /// The ordering. Lower ordering values get called earlier. + public static void RegisterDomainUnload(CallbackFunction callback, int ordering = 0) + { + if (k_DomainUnloadMethods == null) + { + k_DomainUnloadMethods = new List(); + var go = new GameObject(); + go.AddComponent().IsActive = true; + go.hideFlags = HideFlags.HideInHierarchy; + if (Application.isPlaying) + UnityEngine.Object.DontDestroyOnLoad(go); + else + go.hideFlags = HideFlags.HideAndDontSave; + } + + k_DomainUnloadMethods.Add(new UnloadMethod { Function = callback, Ordering = ordering }); + } + + internal static void InvokeBeforeDomainUnload() + { + if (k_DomainUnloadMethods != null) + { + InvokeMethods(k_DomainUnloadMethods); + } + + k_DomainUnloadMethods = null; + } + + static void InvokeMethods(List callbacks) + { + callbacks.Sort(); + + foreach (var m in callbacks) + { + var callback = m.Function; + +#if !UNITY_WINRT + UnityEngine.Profiling.Profiler.BeginSample(callback.Method.DeclaringType.Name + "." + callback.Method.Name); +#endif + + // Isolate systems from each other + try + { + callback(); + } + catch (Exception exc) + { + Debug.LogException(exc); + } + + +#if !UNITY_WINRT + UnityEngine.Profiling.Profiler.EndSample(); +#endif + } + } + } +} diff --git a/Unity.Entities.Hybrid/PlayerLoopManager.cs.meta b/Unity.Entities.Hybrid/PlayerLoopManager.cs.meta new file mode 100644 index 00000000..1a3d140a --- /dev/null +++ b/Unity.Entities.Hybrid/PlayerLoopManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 351cdd05f4a964d7ba56b3fb71527797 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Hybrid/Unity.Entities.Hybrid.asmdef b/Unity.Entities.Hybrid/Unity.Entities.Hybrid.asmdef new file mode 100644 index 00000000..7ab941dc --- /dev/null +++ b/Unity.Entities.Hybrid/Unity.Entities.Hybrid.asmdef @@ -0,0 +1,12 @@ +{ + "name": "Unity.Entities.Hybrid", + "references": [ + "Unity.Entities", + "Unity.Collections", + "Unity.Jobs" + ], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true +} diff --git a/Unity.Entities.Hybrid/Unity.Entities.Hybrid.asmdef.meta b/Unity.Entities.Hybrid/Unity.Entities.Hybrid.asmdef.meta new file mode 100644 index 00000000..997c3da5 --- /dev/null +++ b/Unity.Entities.Hybrid/Unity.Entities.Hybrid.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8819f35a0fc84499b990e90a4ca1911f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Properties.Tests.meta b/Unity.Entities.Properties.Tests.meta new file mode 100644 index 00000000..f463e8ad --- /dev/null +++ b/Unity.Entities.Properties.Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 60cf6e3f38c47e946b0b4b4a4fca20bb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Properties.Tests/EntitySerializationPerformanceTests.cs b/Unity.Entities.Properties.Tests/EntitySerializationPerformanceTests.cs new file mode 100644 index 00000000..399fe4fe --- /dev/null +++ b/Unity.Entities.Properties.Tests/EntitySerializationPerformanceTests.cs @@ -0,0 +1,271 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using NUnit.Framework; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Properties.Serialization; +using UnityEngine; +using Unity.Properties; +using UnityEngine.Profiling; +using Debug = UnityEngine.Debug; + +namespace Unity.Entities.Properties.Tests +{ + [TestFixture] + public sealed class EntitySerializationPerformanceTests + { + private World m_PreviousWorld; + private World m_World; + private EntityManager m_Manager; + + [SetUp] + public void Setup() + { + m_PreviousWorld = World.Active; + m_World = World.Active = new World("Test World"); + m_Manager = m_World.GetOrCreateManager(); + } + + [TearDown] + public void TearDown() + { + if (m_Manager != null) + { + m_World.Dispose(); + m_World = null; + + World.Active = m_PreviousWorld; + m_PreviousWorld = null; + m_Manager = null; + } + } + + /// + /// Serializes 100,000 entities as json + /// + [Test] + public void SerializationPerformance() + { + const int kCount = 100000; + + // Create kCount entities and assign some arbitrary component data + for (var i = 0; i < kCount; ++i) + { + var entity = m_Manager.CreateEntity(typeof(TestComponent), typeof(TestComponent2), typeof(MathComponent), typeof(BlitComponent)); + + var comp = m_Manager.GetComponentData(entity); + comp.blit.x = 123f; + comp.blit.y = 456.789; + comp.blit.z = -12; + comp.flt = 0.01f; + + m_Manager.SetComponentData(entity, comp); + } + + // Create a reusable string buffer and JsonVisitor + var buffer = new StringBuffer(4096); + var visitor = new JsonVisitor { StringBuffer = buffer }; + + using (var entities = m_Manager.GetAllEntities()) + { + // Since we are testing raw serialization performance we pre warm the property type bag + // This builds a property tree for each type + // This is done on demand for newly discovered types + // @NOTE This json string will also be used to debug the size for a single entity + var container = new EntityContainer(m_Manager, entities[0]); + + var json = JsonSerializer.Serialize(ref container); + + var totalTimer = new Stopwatch(); + totalTimer.Start(); + + foreach (var entity in entities) + { + container = new EntityContainer(m_Manager, entity); + + // Visit and write to the underlying StringBuffer, this is the raw json serialization + JsonSerializer.Serialize(ref container, visitor); + + // @NOTE at this point we can call Write(buffer.Buffer, 0, buffer.Length) + buffer.Clear(); + } + + totalTimer.Stop(); + + Debug.Log($"Serialized {kCount} entities in {totalTimer.Elapsed}. Size per entity = {json.Length} bytes, total size = {json.Length * kCount} bytes"); + Debug.Log(json); + } + } + + private struct SerializationJob : IJobParallelForBatch + { + [ReadOnly] + public NativeArray Entities; + + public void Execute(int startIndex, int count) + { + // @HACK need a reliable way having the entity manage for the given entities + var manager = World.Active.GetExistingManager(); + var buffer = new StringBuffer(4096); + var visitor = new JsonVisitor { StringBuffer = buffer }; + + var end = startIndex + count; + for (var i = startIndex; i < end; i++) + { + var container = new EntityContainer(manager, Entities[i]); + JsonSerializer.Serialize(ref container, visitor); + + // @NOTE at this point we can call Write(buffer.Buffer, 0, buffer.Length) + buffer.Clear(); + } + } + } + + private struct WorkerThreadContext + { + public NativeArray Entities; + public int StartIndex; + public int EndIndex; + public string Output; + } + + /// + /// Serializes 100,000 entities as json using manual thread management + /// + /// This test exists as an example to quickly test stuff on the thread that is not supported by C# job system + /// (e.g. disc I/O, managed objects, strings etc) + /// + [Test] + public void SerializationPerformanceThreaded() + { + const int kCount = 100000; + + // Create kCount entities and assign some arbitrary component data + for (var i = 0; i < kCount; ++i) + { + var entity = m_Manager.CreateEntity(typeof(TestComponent), typeof(TestComponent2), typeof(MathComponent), typeof(BlitComponent)); + + var comp = m_Manager.GetComponentData(entity); + comp.blit.x = 123f; + comp.blit.y = 456.789; + comp.blit.z = -12; + comp.flt = 0.01f; + + m_Manager.SetComponentData(entity, comp); + } + + using (var entities = m_Manager.GetAllEntities()) + { + // Since we are testing raw serialization performance we rre warm the property type bag + // This builds a property tree for each type + // This is done on demand for newly discovered types + // @NOTE This json string will also be used to debug the size for a single entity + var container = new EntityContainer(m_Manager, entities[0]); + + var json = JsonSerializer.Serialize(ref container); + + var totalTimer = new Stopwatch(); + + totalTimer.Start(); + + var numThreads = Math.Max(1, Environment.ProcessorCount - 1); + var threadCount = numThreads; + var countPerThread = entities.Length / threadCount + 1; + var threads = new Thread[threadCount]; + + // Split the workload 'evenly' across numThreads (IJobParallelForBatch) + for (int begin = 0, index = 0; begin < entities.Length; begin += countPerThread, index++) + { + var context = new WorkerThreadContext + { + Entities = entities, + StartIndex = begin, + EndIndex = Mathf.Min(begin + countPerThread, entities.Length) + }; + + var thread = new Thread(obj => + { + var buffer = new StringBuffer(4096); + var visitor = new JsonVisitor { StringBuffer = buffer }; + + var c = (WorkerThreadContext)obj; + for (int p = c.StartIndex, end = c.EndIndex; p < end; p++) + { + var entity = c.Entities[p]; + + container = new EntityContainer(m_Manager, entity); + + JsonSerializer.Serialize(ref container, visitor); + + // @NOTE at this point we can call Write(buffer.Buffer, 0, buffer.Length) + buffer.Clear(); + } + }) + { IsBackground = true }; + thread.Start(context); + threads[index] = thread; + } + + foreach (var thread in threads) + { + thread.Join(); + } + + totalTimer.Stop(); + Debug.Log($"Serialized {kCount} entities in {totalTimer.Elapsed}. Size per entity = {json.Length} bytes, total size = {json.Length * kCount} bytes"); + } + } + + /// + /// Serializes 100,000 entities as json using the C# job system + /// + [Test] + public void SerializationPerformanceJob() + { + const int kCount = 100000; + + // Create kCount entities and assign some arbitrary component data + for (var i = 0; i < kCount; ++i) + { + var entity = m_Manager.CreateEntity(typeof(TestComponent), typeof(TestComponent2), typeof(MathComponent), typeof(BlitComponent)); + + var comp = m_Manager.GetComponentData(entity); + comp.blit.x = 123f; + comp.blit.y = 456.789; + comp.blit.z = -12; + comp.flt = 0.01f; + + m_Manager.SetComponentData(entity, comp); + } + + using (var entities = m_Manager.GetAllEntities(Allocator.TempJob)) + { + // Since we are testing raw serialization performance we pre warm the property type bag + // This builds a property tree for each type + // This is done on demand for newly discovered types + // @NOTE This json string will also be used to debug the size for a single entity + + var container = new EntityContainer(m_Manager, entities[0]); + + var json = JsonSerializer.Serialize(ref container); + + var job = new SerializationJob + { + Entities = entities + }; + + var totalTimer = new Stopwatch(); + totalTimer.Start(); + + var handle = job.ScheduleBatch(entities.Length, 10000); + handle.Complete(); + + totalTimer.Stop(); + Debug.Log($"Serialized {kCount} entities in {totalTimer.Elapsed}. Size per entity = {json.Length} bytes, total size = {json.Length * kCount} bytes"); + } + } + } +} diff --git a/Unity.Entities.Properties.Tests/EntitySerializationPerformanceTests.cs.meta b/Unity.Entities.Properties.Tests/EntitySerializationPerformanceTests.cs.meta new file mode 100644 index 00000000..e0f96fcd --- /dev/null +++ b/Unity.Entities.Properties.Tests/EntitySerializationPerformanceTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8150ce4678eb92146ab214616daf9f81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Properties.Tests/EntitySerializationTests.cs b/Unity.Entities.Properties.Tests/EntitySerializationTests.cs new file mode 100644 index 00000000..bde1a974 --- /dev/null +++ b/Unity.Entities.Properties.Tests/EntitySerializationTests.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using NUnit.Framework; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Properties.Serialization; +using UnityEngine; +using Unity.Properties; +using UnityEngine.Profiling; +using Debug = UnityEngine.Debug; + +namespace Unity.Entities.Properties.Tests +{ + [TestFixture] + public sealed class EntitySerializationTests + { + private World m_PreviousWorld; + private World m_World; + private EntityManager m_Manager; + + [SetUp] + public void Setup() + { + m_PreviousWorld = World.Active; + m_World = World.Active = new World ("Test World"); + m_Manager = m_World.GetOrCreateManager (); + } + + [TearDown] + public void TearDown() + { + if (m_Manager != null) + { + m_World.Dispose(); + m_World = null; + + World.Active = m_PreviousWorld; + m_PreviousWorld = null; + m_Manager = null; + } + } + + /// + /// Writes an entity to json + /// + [Test] + public void SimpleFlat() + { + var entity = m_Manager.CreateEntity(typeof(TestComponent), typeof(TestComponent2)); + + var testComponent = m_Manager.GetComponentData(entity); + testComponent.x = 123f; + m_Manager.SetComponentData(entity, testComponent); + + var container = new EntityContainer(m_Manager, entity); + var json = JsonSerializer.Serialize(ref container); + Debug.Log(json); + } + + [Test] + public void SimpleNested() + { + var entity = m_Manager.CreateEntity(typeof(NestedComponent)); + + var nestedComponent = m_Manager.GetComponentData(entity); + nestedComponent.test.x = 123f; + m_Manager.SetComponentData(entity, nestedComponent); + + var container = new EntityContainer(m_Manager, entity); + var json = JsonSerializer.Serialize(ref container); + Debug.Log(json); + } + + [Test] + public void MathOverrides() + { + var entity = m_Manager.CreateEntity(typeof(MathComponent)); + + var math = m_Manager.GetComponentData(entity); + math.v2 = new float2(1f, 2f); + math.v3 = new float3(1f, 2f, 3f); + math.v4 = new float4(1f, 2f, 3f, 4f); + m_Manager.SetComponentData(entity, math); + + var container = new EntityContainer(m_Manager, entity); + var json = JsonSerializer.Serialize(ref container); + Debug.Log(json); + } + + [Test] + public void BlittableTest() + { + var entity = m_Manager.CreateEntity(typeof(BlitComponent)); + + var comp = m_Manager.GetComponentData(entity); + comp.blit.x = 123f; + comp.blit.y = 456.789; + comp.blit.z = -12; + comp.flt = 0.01f; + + var container = new EntityContainer(m_Manager, entity); + var json = JsonSerializer.Serialize(ref container); + Debug.Log(json); + } + } +} diff --git a/Unity.Entities.Properties.Tests/EntitySerializationTests.cs.meta b/Unity.Entities.Properties.Tests/EntitySerializationTests.cs.meta new file mode 100644 index 00000000..54afbba4 --- /dev/null +++ b/Unity.Entities.Properties.Tests/EntitySerializationTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9673b4ba67b640388eb2fc2f3e2ec03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Properties.Tests/TestComponents.cs b/Unity.Entities.Properties.Tests/TestComponents.cs new file mode 100644 index 00000000..445bfdc6 --- /dev/null +++ b/Unity.Entities.Properties.Tests/TestComponents.cs @@ -0,0 +1,52 @@ +using Unity.Entities; +using Unity.Mathematics; + + +namespace Unity.Entities.Properties.Tests +{ + public struct TestComponent : IComponentData + { + public float x; + } + + public struct TestComponent2 : IComponentData + { + public int x; + public byte b; + } + + public struct MathComponent : IComponentData + { + public float2 v2; + public float3 v3; + public float4 v4; + public float2x2 m2; + public float3x3 m3; + public float4x4 m4; + } + + public struct NestedComponent : IComponentData + { + public TestComponent test; + } + + public struct BlitMe + { + public float x; + public double y; + public sbyte z; + } + + public struct BlitComponent : IComponentData + { + public BlitMe blit; + public float flt; + } + + public struct TestSharedComponent : ISharedComponentData + { + public float value; + + public TestSharedComponent(float v) { value = v; } + } +} diff --git a/Unity.Entities.Properties.Tests/TestComponents.cs.meta b/Unity.Entities.Properties.Tests/TestComponents.cs.meta new file mode 100644 index 00000000..e4ac1808 --- /dev/null +++ b/Unity.Entities.Properties.Tests/TestComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 608d41d9ba4f9c5488b39181447bf80e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Properties.Tests/Unity.Entities.Properties.Tests.asmdef b/Unity.Entities.Properties.Tests/Unity.Entities.Properties.Tests.asmdef new file mode 100644 index 00000000..a5872466 --- /dev/null +++ b/Unity.Entities.Properties.Tests/Unity.Entities.Properties.Tests.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Unity.Entities.Properties.Tests", + "references": [ + "Unity.Entities", + "Unity.Mathematics", + "Unity.Properties", + "Unity.Jobs", + "Unity.Entities.Properties" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [ + "Editor" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true +} diff --git a/Unity.Entities.Properties.Tests/Unity.Entities.Properties.Tests.asmdef.meta b/Unity.Entities.Properties.Tests/Unity.Entities.Properties.Tests.asmdef.meta new file mode 100644 index 00000000..369b072a --- /dev/null +++ b/Unity.Entities.Properties.Tests/Unity.Entities.Properties.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9e1040f5c9cc1e44b99fa851e38481e5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Properties.meta b/Unity.Entities.Properties.meta new file mode 100644 index 00000000..1dde31e5 --- /dev/null +++ b/Unity.Entities.Properties.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05c1d07d3d669c34e92c1bf078bbba84 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Properties/EntityContainer.cs b/Unity.Entities.Properties/EntityContainer.cs new file mode 100644 index 00000000..c701bd60 --- /dev/null +++ b/Unity.Entities.Properties/EntityContainer.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using Unity.Properties; + +namespace Unity.Entities.Properties +{ + /// + /// Container to iterate on Entity instances. + /// + public unsafe struct EntityContainer : IPropertyContainer + { + /// + /// WARNING This property does NOT implement the List property fully and instead makes the assumption that we are only serializing... + /// This may cause problems when we start to write UI code and should be looked at. + /// This is a quick implementation to get higher performance visits + /// + private sealed class ReadOnlyComponentsProperty : StructMutableContainerListProperty, StructProxy> + { + public ReadOnlyComponentsProperty(string name) : base(name, null, null) { } + + public override void Accept(ref EntityContainer container, IPropertyVisitor visitor) + { + var count = container.m_Manager.GetComponentCount(container.m_Entity); + var listContext = new VisitContext> {Property = this, Value = null, Index = -1 }; + + + // @TODO improve, split the deps + HashSet primitiveTypes = new HashSet(); + // try to gather the primitive types for that visitor + var entityVisitor = visitor as IPrimitivePropertyVisitor; + if (entityVisitor != null) + { + primitiveTypes = entityVisitor.SupportedPrimitiveTypes(); + } + else + { + // @TODO remove that dependency + // Fallback on the optimized visitor for now + primitiveTypes = OptimizedVisitor.SupportedTypes(); + } + + if (visitor.BeginList(ref container, listContext)) + { + for (var i = 0; i < count; i++) + { + var item = Get(ref container, i, primitiveTypes); + var context = new VisitContext + { + Property = this, + Value = item, + Index = i + }; + + if (visitor.BeginContainer(ref container, context)) + { + item.PropertyBag.Visit(ref item, visitor); + } + + visitor.EndContainer(ref container, context); + } + } + + visitor.EndList(ref container, listContext); + } + + private static StructProxy Get(ref EntityContainer container, int index, HashSet primitiveTypes) + { + var typeIndex = container.m_Manager.GetComponentTypeIndex(container.m_Entity, index); + var propertyType = TypeManager.GetType(typeIndex); + var propertyBag = TypeInformation.GetOrCreate(propertyType, primitiveTypes); + var data = (byte*) container.m_Manager.GetComponentDataRawRW(container.m_Entity, typeIndex); + + var p = new StructProxy + { + bag = propertyBag, + data = data, + type = propertyType + }; + + return p; + } + } + + private static readonly IListProperty s_ComponentsProperty = new ReadOnlyComponentsProperty( + "Components"); + + private static readonly PropertyBag s_PropertyBag = new PropertyBag(s_ComponentsProperty); + + private readonly EntityManager m_Manager; + private readonly Entity m_Entity; + + public IVersionStorage VersionStorage => PassthroughVersionStorage.Instance; + public IPropertyBag PropertyBag => s_PropertyBag; + + public EntityContainer(EntityManager manager, Entity entity) + { + m_Manager = manager; + m_Entity = entity; + } + } +} diff --git a/Unity.Entities.Properties/EntityContainer.cs.meta b/Unity.Entities.Properties/EntityContainer.cs.meta new file mode 100644 index 00000000..aa1a9462 --- /dev/null +++ b/Unity.Entities.Properties/EntityContainer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b98f8570a11344a8890063dcdb4a187b +timeCreated: 1518524448 \ No newline at end of file diff --git a/Unity.Entities.Properties/JsonVisitor.cs b/Unity.Entities.Properties/JsonVisitor.cs new file mode 100644 index 00000000..f0d37d8d --- /dev/null +++ b/Unity.Entities.Properties/JsonVisitor.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using Unity.Properties; +using Unity.Properties.Serialization; + +namespace Unity.Entities.Properties +{ + public interface IPrimitivePropertyVisitor + { + // @TODO decouple from visitor ... + HashSet SupportedPrimitiveTypes(); + } + + public interface IOptimizedVisitor : + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit, + ICustomVisit + { } + + public static class OptimizedVisitor + { + public static bool Supports(Type t) + { + return s_OptimizedSet.Contains(t); + } + + public static HashSet SupportedTypes() + { + return s_OptimizedSet; + } + + private static HashSet s_OptimizedSet; + + static OptimizedVisitor() + { + s_OptimizedSet = new HashSet(); + foreach (var it in typeof(IOptimizedVisitor).GetInterfaces()) + { + if (it.IsGenericType && typeof(ICustomVisit<>) == it.GetGenericTypeDefinition()) + { + var genArgs = it.GetGenericArguments(); + if (genArgs.Length == 1) + { + s_OptimizedSet.Add(genArgs[0]); + } + } + } + } + } + + public static class StringBufferExtensions + { + public static void AppendPropertyName(this StringBuffer sb, string propertyName) + { + sb.EnsureCapacity(propertyName.Length + 4); + + var buffer = sb.Buffer; + var position = sb.Length; + + buffer[position++] = '\"'; + + var len = propertyName.Length; + for (var i = 0; i < len; i++) + { + buffer[position + i] = propertyName[i]; + } + position += len; + + buffer[position++] = '\"'; + buffer[position++] = ':'; + buffer[position++] = ' '; + + sb.Length = position; + } + + public static void AppendFloat2(this StringBuffer sb, float2 value) + { + sb.Append(value.x); + sb.Append(','); + sb.Append(value.y); + } + + public static void AppendFloat3(this StringBuffer sb, float3 value) + { + sb.Append(value.x); + sb.Append(','); + sb.Append(value.y); + sb.Append(','); + sb.Append(value.z); + } + + public static void AppendFloat4(this StringBuffer sb, float4 value) + { + sb.Append(value.x); + sb.Append(','); + sb.Append(value.y); + sb.Append(','); + sb.Append(value.z); + sb.Append(','); + sb.Append(value.w); + } + } + + public class JsonVisitor : JsonPropertyVisitor, IOptimizedVisitor + { + public HashSet SupportedPrimitiveTypes() + { + return OptimizedVisitor.SupportedTypes(); + } + + void ICustomVisit.CustomVisit(float2 value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append('['); + StringBuffer.AppendFloat2(value); + StringBuffer.Append(']'); + StringBuffer.Append(",\n"); + } + + void ICustomVisit.CustomVisit(float3 value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append('['); + StringBuffer.AppendFloat3(value); + StringBuffer.Append(']'); + StringBuffer.Append(",\n"); + } + + void ICustomVisit.CustomVisit(float4 value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append('['); + StringBuffer.AppendFloat4(value); + StringBuffer.Append(']'); + StringBuffer.Append(",\n"); + } + + void ICustomVisit.CustomVisit(float2x2 value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append('['); + StringBuffer.AppendFloat2(value.m0); + StringBuffer.Append(','); + StringBuffer.AppendFloat2(value.m1); + StringBuffer.Append(']'); + StringBuffer.Append(",\n"); + } + + void ICustomVisit.CustomVisit(sbyte value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append(value); + StringBuffer.Append(",\n"); + } + + void ICustomVisit.CustomVisit(byte value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append(value); + StringBuffer.Append(",\n"); + } + + void ICustomVisit.CustomVisit(int value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append(value); + StringBuffer.Append(",\n"); + } + void ICustomVisit.CustomVisit(string value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append(value); + StringBuffer.Append(",\n"); + } + + void ICustomVisit.CustomVisit(float3x3 value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append('['); + StringBuffer.AppendFloat3(value.m0); + StringBuffer.Append(','); + StringBuffer.AppendFloat3(value.m1); + StringBuffer.Append(','); + StringBuffer.AppendFloat3(value.m2); + StringBuffer.Append(']'); + StringBuffer.Append(",\n"); + } + + void ICustomVisit.CustomVisit(float4x4 value) + { + StringBuffer.Append(' ', Style.Space * Indent); + StringBuffer.AppendPropertyName(Property.Name); + StringBuffer.Append('['); + StringBuffer.AppendFloat4(value.m0); + StringBuffer.Append(','); + StringBuffer.AppendFloat4(value.m1); + StringBuffer.Append(','); + StringBuffer.AppendFloat4(value.m2); + StringBuffer.Append(','); + StringBuffer.AppendFloat4(value.m3); + StringBuffer.Append(']'); + StringBuffer.Append(",\n"); + } + } +} diff --git a/Unity.Entities.Properties/JsonVisitor.cs.meta b/Unity.Entities.Properties/JsonVisitor.cs.meta new file mode 100644 index 00000000..b897733e --- /dev/null +++ b/Unity.Entities.Properties/JsonVisitor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e39736ee9a3040288016256267d8ac6b +timeCreated: 1518695501 \ No newline at end of file diff --git a/Unity.Entities.Properties/StructProxy.cs b/Unity.Entities.Properties/StructProxy.cs new file mode 100644 index 00000000..80edac03 --- /dev/null +++ b/Unity.Entities.Properties/StructProxy.cs @@ -0,0 +1,15 @@ +using System; +using Unity.Properties; + +namespace Unity.Entities.Properties +{ + public unsafe struct StructProxy : IPropertyContainer + { + public IVersionStorage VersionStorage => PassthroughVersionStorage.Instance; + public IPropertyBag PropertyBag => bag; + + public byte* data; + public PropertyBag bag; + public Type type; + } +} diff --git a/Unity.Entities.Properties/StructProxy.cs.meta b/Unity.Entities.Properties/StructProxy.cs.meta new file mode 100644 index 00000000..2a978e41 --- /dev/null +++ b/Unity.Entities.Properties/StructProxy.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0cacc0e67a054403a470023326226e9e +timeCreated: 1518683734 \ No newline at end of file diff --git a/Unity.Entities.Properties/TypeInformation.cs b/Unity.Entities.Properties/TypeInformation.cs new file mode 100644 index 00000000..2a1f752f --- /dev/null +++ b/Unity.Entities.Properties/TypeInformation.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Xml.Serialization; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Properties; +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Unity.Entities.Properties +{ + public interface ITypeVisitor + { + IPropertyContainer VisitType(ITypedMemberDescriptor type, out bool recurse); + } + + public interface ITypedMemberDescriptor + { + int GetOffset(); + Type GetMemberType(); + bool IsPrimitive(); + string Name { get; } + } + + public class FieldMemberDescriptor : ITypedMemberDescriptor + { + private FieldInfo Info { get; set; } + + public FieldMemberDescriptor(FieldInfo field) + { + Info = field; + } + + public Type GetMemberType() + { + return Info.FieldType; + } + + public bool IsPrimitive() + { + return Info.FieldType.IsPrimitive; + } + + public int GetOffset() + { + return UnsafeUtility.GetFieldOffset(Info); + } + + public string Name + { + get { return Info.Name; } + } + } + + public class PropertyMemberDescriptor : ITypedMemberDescriptor + { + private PropertyInfo Info { get; set; } + + public PropertyMemberDescriptor(PropertyInfo p) + { + Info = p; + } + + public Type GetMemberType() + { + return Info.PropertyType; + } + + public bool IsPrimitive() + { + return Info.PropertyType.IsPrimitive; + } + public string Name + { + get { return Info.Name; } + } + + public int GetOffset() + { + // TODO(WIP) This is broken for now obviously ... + return 0; + } + } + + public class TypeFieldIterator + { + [Flags] + public enum Specifier + { + Public = 0x01, + Private = 0x02, + ValueType = 0x04, + } + + public TypeFieldIterator(Type t) + { + _t = t; + } + + public IEnumerable Get(Specifier d) + { + if (_t == null) + { + yield return null; + } + foreach (var field in _t.GetFields()) + { + if (field.IsStatic) + { + continue; + } + if (field.FieldType.IsValueType != d.HasFlag(Specifier.ValueType)) + { + continue; + } + if (field.IsPublic && d.HasFlag(Specifier.Public)) + { + yield return new FieldMemberDescriptor(field); + } + if (!field.IsPublic && d.HasFlag(Specifier.Private)) + { + yield return new FieldMemberDescriptor(field); + } + } + } + private Type _t; + } + + public class TypePropertyIterator + { + [Flags] + public enum Specifier + { + Public = 0x01, + Private = 0x02, + ValueType = 0x04, + } + + public TypePropertyIterator(Type t) + { + _t = t; + } + + public IEnumerable Get(Specifier d) + { + if (_t == null) + { + yield break; + } + yield break; +#if ENABLE_CSHARP_PROPERTY_PARSING + foreach (var p in _t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (!p.CanRead || !p.GetGetMethod(true).IsPublic) + { + continue; + } + + // we dont support recursion for now ... + if (_t == p.PropertyType) + { + continue; + } + + if (p.PropertyType.IsValueType != d.HasFlag(Specifier.ValueType)) + { + continue; + } + + yield return new PropertyMemberDescriptor(p); + } +#endif + } + private Type _t; + } + + public class PropertyCodeReflectionParser + { + public HashSet PrimitiveTypes { get; set; } + + private class TypeTreeParsingContext + { + public int Offset { get; set; } = 0; + } + + public PropertyBag Parse(Type t) + { + return DoParse(t, new TypeTreeParsingContext()); + } + + private PropertyBag DoParse(Type t, TypeTreeParsingContext context) + { + if (_propertyBagCache.ContainsKey(t)) + { + return _propertyBagCache[t]; + } + + Dictionary properties = new Dictionary() + { + { ComponentIdProperty.Name, ComponentIdProperty } + }; + + foreach (var member in new TypeFieldIterator(t).Get( + TypeFieldIterator.Specifier.Public | TypeFieldIterator.Specifier.ValueType)) + { + IProperty property = VisitType(member, new TypeTreeParsingContext() { Offset = context.Offset + member.GetOffset() }); + if (!properties.ContainsKey(property.Name)) + { + properties[property.Name] = property; + } + } + + foreach (var member in new TypePropertyIterator(t).Get( + TypePropertyIterator.Specifier.Public | TypePropertyIterator.Specifier.ValueType)) + { + try + { + IProperty property = VisitType(member, new TypeTreeParsingContext() { Offset = context.Offset }); + + // TODO Unknown types: GameObjects, etc + if (property != null) + { + if (!properties.ContainsKey(property.Name)) + { + properties[property.Name] = property; + } + } + } + catch (Exception) + { } + } + + _propertyBagCache[t] = new PropertyBag(properties.Values.ToList()); + + return _propertyBagCache[t]; + } + + private IProperty VisitType(ITypedMemberDescriptor d, TypeTreeParsingContext context) + { + Type memberType = d.GetMemberType(); + if (!typeof(IComponentData).IsAssignableFrom(memberType)) + { + if (memberType.IsEnum) + { + // Same hack as below + // TODO: this is a hack until we have code gen + var propertyType = typeof(EnumPrimitiveProperty<>).MakeGenericType(memberType); + return (IProperty)Activator.CreateInstance(propertyType, d); + } + else if (PrimitiveTypes.Contains(d.GetMemberType())) + { + var propertyType = typeof(PrimitiveProperty<>).MakeGenericType(memberType); + return (IProperty)Activator.CreateInstance(propertyType, d); + } + + if (memberType.IsPrimitive) + { + throw new NotSupportedException($"Primitive field type {memberType} is not supported"); + } + } + + return new NestedProxyProperty(d) { PropertyBag = Parse(memberType) }; + } + + private class TypeIdProperty : StructProperty + { + public TypeIdProperty(GetValueMethod getValue) : base("$TypeId", getValue, null) + { + } + } + + private static readonly IProperty ComponentIdProperty = new TypeIdProperty( + (ref StructProxy c) => c.type.FullName); + + private readonly Dictionary _propertyBagCache = new Dictionary(); + + private unsafe class NestedProxyProperty : StructMutableContainerProperty + { + public int FieldOffset { get; } + public Type ComponentType { get; } + public PropertyBag PropertyBag { get; set; } + + public NestedProxyProperty(ITypedMemberDescriptor member) + : base(member.Name, null, null, null) + { + FieldOffset = member.GetOffset(); + ComponentType = member.GetMemberType(); + RefAccess = GetChildRef; + } + + private void GetChildRef(ref StructProxy container, RefVisitMethod refVisitMethod, IPropertyVisitor visitor) + { + var val = GetValue(ref container); + refVisitMethod(ref val, visitor); + } + + public override StructProxy GetValue(ref StructProxy container) + { + return new StructProxy() + { + data = container.data + FieldOffset, + bag = PropertyBag, + type = ComponentType + }; + } + } + + private unsafe class PrimitiveProperty : StructProperty + where TValue : struct + { + // TODO only temporary for property wrappers + private MethodInfo PropertyGetMethod { get; } + private MethodInfo PropertySetMethod { get; } + + private int FieldOffset { get; } + + public override bool IsReadOnly => false; + + public PrimitiveProperty(ITypedMemberDescriptor member) : base(member.Name, null, null) + { + FieldOffset = member.GetOffset(); + + /* + Type myType = typeof(PrimitiveProperty<>.).MakeGenericType(p.PropertyType); + PropertyGetMethod = new DynamicMethod(p.PropertyType.Name, p.PropertyType, new Type[] { typeof(void*) }); + ILGenerator gen = PropertyGetMethod.GetILGenerator(); + LocalBuilder outputValue = gen.DeclareLocal(p.PropertyType); + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldloc_0); + gen.Emit(OpCodes.Call, UnsafeUtility.CopyPtrToStructure); + gen.Emit(OpCodes.Ldarg_0); // ?? + gen.Emit(OpCodes.Call, p.GetMethod); + gen.Emit(OpCodes.Ret); + */ + } + + public override TValue GetValue(ref StructProxy container) + { + TValue v = default(TValue); + UnsafeUtility.CopyPtrToStructure(container.data + FieldOffset, out v); + return v; + } + + public override void SetValue(ref StructProxy container, TValue value) + { + // @TODO ComponentJobSafetyManager.CompleteReadAndWriteDependency + UnsafeUtility.CopyStructureToPtr(ref value, container.data + FieldOffset); + } + } + + private unsafe class EnumPrimitiveProperty : StructEnumProperty + where TValue : struct, IComparable, IFormattable, IConvertible + { + private int FieldOffset { get; } + + public EnumPrimitiveProperty(ITypedMemberDescriptor member) : base(member.Name, null, null) + { + FieldOffset = member.GetOffset(); + } + + public override TValue GetValue(ref StructProxy container) + { + TValue v = default(TValue); +// UnsafeUtility.CopyPtrToStructure(container.data + FieldOffset, out v); + UnsafeUtility.MemCpy(container.data + FieldOffset, UnsafeUtility.AddressOf(ref v), UnsafeUtility.SizeOf()); + return v; + } + + public override void SetValue(ref StructProxy container, TValue value) + { + // @TODO ComponentJobSafetyManager.CompleteReadAndWriteDependency + UnsafeUtility.CopyStructureToPtr(ref value, container.data + FieldOffset); + } + } + } + + public static class TypeInformation + { + private static PropertyCodeReflectionParser _propertyTypeParser = new PropertyCodeReflectionParser(); + + public static PropertyBag GetOrCreate(Type componentType, HashSet primitiveTypes) + { + _propertyTypeParser.PrimitiveTypes = primitiveTypes; + return _propertyTypeParser.Parse(componentType); + } + } +} diff --git a/Unity.Entities.Properties/TypeInformation.cs.meta b/Unity.Entities.Properties/TypeInformation.cs.meta new file mode 100644 index 00000000..a906d53c --- /dev/null +++ b/Unity.Entities.Properties/TypeInformation.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 973c0145e0f0485a8a04b25bbfe0bc3f +timeCreated: 1518713095 \ No newline at end of file diff --git a/Unity.Entities.Properties/Unity.Entities.Properties.asmdef b/Unity.Entities.Properties/Unity.Entities.Properties.asmdef new file mode 100644 index 00000000..6a34405b --- /dev/null +++ b/Unity.Entities.Properties/Unity.Entities.Properties.asmdef @@ -0,0 +1,13 @@ +{ + "name": "Unity.Entities.Properties", + "references": [ + "Unity.Entities", + "Unity.Mathematics", + "Unity.Properties", + "Unity.Jobs", + "Unity.Transforms" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true +} diff --git a/Unity.Entities.Properties/Unity.Entities.Properties.asmdef.meta b/Unity.Entities.Properties/Unity.Entities.Properties.asmdef.meta new file mode 100644 index 00000000..50006a9f --- /dev/null +++ b/Unity.Entities.Properties/Unity.Entities.Properties.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 95b0bb42dc1994c5784e4ecf790b879f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests.meta b/Unity.Entities.Tests.meta new file mode 100644 index 00000000..cbe158d6 --- /dev/null +++ b/Unity.Entities.Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6921edad958614db88329a480a08b941 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/ArchetypeChunkArrayTests.cs b/Unity.Entities.Tests/ArchetypeChunkArrayTests.cs new file mode 100644 index 00000000..a5988f69 --- /dev/null +++ b/Unity.Entities.Tests/ArchetypeChunkArrayTests.cs @@ -0,0 +1,412 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Jobs; +using System; +using System.Collections.Generic; +using Unity.Mathematics; + +namespace Unity.Entities.Tests +{ + + + [TestFixture] + public class ArchetypeChunkArrayTest : ECSTestsFixture + { + public Entity CreateEntity(int value, int sharedValue) + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestSharedComp)); + m_Manager.SetComponentData(entity, new EcsTestData(value)); + m_Manager.SetSharedComponentData(entity, new EcsTestSharedComp(sharedValue)); + return entity; + } + + public Entity CreateEntity2(int value, int sharedValue) + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData2), typeof(EcsTestSharedComp)); + m_Manager.SetComponentData(entity, new EcsTestData2(value)); + m_Manager.SetSharedComponentData(entity, new EcsTestSharedComp(sharedValue)); + return entity; + } + + void CreateEntities(int count) + { + for (int i = 0; i != count; i++) + CreateEntity(i, i % 7); + } + + void CreateMixedEntities(int count) + { + for (int i = 0; i != count; i++) + { + if ((i & 1) == 0) + CreateEntity(i, i % 7); + else + CreateEntity2(-i, i % 7); + } + } + + struct CollectValues : IJobParallelFor + { + [ReadOnly] public ArchetypeChunkArray chunks; + [ReadOnly] public ArchetypeChunkComponentType ecsTestData; + + [NativeDisableParallelForRestriction] public NativeArray values; + + public void Execute(int chunkIndex) + { + var chunk = chunks[chunkIndex]; + var chunkStartIndex = chunk.StartIndex; + var chunkCount = chunk.Count; + var chunkEcsTestData = chunk.GetNativeArray(ecsTestData); + + for (int i = 0; i < chunkCount; i++) + { + values[chunkStartIndex + i] = chunkEcsTestData[i].value; + } + } + } + + [Test] + public void ACS_BasicIteration() + { + CreateEntities(64); + + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsTestData)}, // all + Allocator.Temp); + + var ecsTestData = m_Manager.GetArchetypeChunkComponentType(true); + var entityCount = chunks.EntityCount; + var values = new NativeArray(entityCount, Allocator.TempJob); + var collectValuesJob = new CollectValues + { + chunks = chunks, + ecsTestData = ecsTestData, + values = values + }; + Assert.AreEqual(7,chunks.Length); + + var collectValuesJobHandle = collectValuesJob.Schedule(chunks.Length, 64); + collectValuesJobHandle.Complete(); + chunks.Dispose(); + + ulong foundValues = 0; + for (int i = 0; i < entityCount; i++) + { + foundValues |= ((ulong)1 << values[i]); + } + + foundValues++; + Assert.AreEqual(0,foundValues); + + values.Dispose(); + } + + struct CollectMixedValues : IJobParallelFor + { + [ReadOnly] public ArchetypeChunkArray chunks; + [ReadOnly] public ArchetypeChunkComponentType ecsTestData; + [ReadOnly] public ArchetypeChunkComponentType ecsTestData2; + + [NativeDisableParallelForRestriction] public NativeArray values; + + public void Execute(int chunkIndex) + { + var chunk = chunks[chunkIndex]; + var chunkStartIndex = chunk.StartIndex; + var chunkCount = chunk.Count; + var chunkEcsTestData = chunk.GetNativeArray(ecsTestData); + var chunkEcsTestData2 = chunk.GetNativeArray(ecsTestData2); + + if (chunkEcsTestData.Length > 0) + { + for (int i = 0; i < chunkCount; i++) + { + values[chunkStartIndex + i] = chunkEcsTestData[i].value; + } + } + else if (chunkEcsTestData2.Length > 0) + { + for (int i = 0; i < chunkCount; i++) + { + values[chunkStartIndex + i] = chunkEcsTestData2[i].value0; + } + } + } + } + + [Test] + public void ACS_FindMixed() + { + CreateMixedEntities(64); + + var chunks = m_Manager.CreateArchetypeChunkArray( + new ComponentType[] {typeof(EcsTestData2), typeof(EcsTestData)}, // any + Array.Empty(), // none + Array.Empty(), // all + Allocator.Temp); + + Assert.AreEqual(14,chunks.Length); + + var ecsTestData = m_Manager.GetArchetypeChunkComponentType(true); + var ecsTestData2 = m_Manager.GetArchetypeChunkComponentType(true); + var entityCount = chunks.EntityCount; + var values = new NativeArray(entityCount, Allocator.TempJob); + var collectValuesJob = new CollectMixedValues + { + chunks = chunks, + ecsTestData = ecsTestData, + ecsTestData2 = ecsTestData2, + values = values + }; + + var collectValuesJobHandle = collectValuesJob.Schedule(chunks.Length, 64); + collectValuesJobHandle.Complete(); + + for (int chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++) + { + var chunk = chunks[chunkIndex]; + var chunkCount = chunk.Count; + + Assert.AreEqual(4,math.ceil_pow2(chunkCount-1)); + } + chunks.Dispose(); + + ulong foundValues = 0; + for (int i = 0; i < entityCount; i++) + { + foundValues |= ((ulong) 1 << math.abs(values[i])); + } + + foundValues++; + Assert.AreEqual(0,foundValues); + + values.Dispose(); + } + + struct ChangeMixedValues : IJobParallelFor + { + [ReadOnly] public ArchetypeChunkArray chunks; + public ArchetypeChunkComponentType ecsTestData; + public ArchetypeChunkComponentType ecsTestData2; + + public void Execute(int chunkIndex) + { + var chunk = chunks[chunkIndex]; + var chunkCount = chunk.Count; + var chunkEcsTestData = chunk.GetNativeArray(ecsTestData); + var chunkEcsTestData2 = chunk.GetNativeArray(ecsTestData2); + + if (chunkEcsTestData.Length > 0) + { + for (int i = 0; i < chunkCount; i++) + { + chunkEcsTestData[i] = new EcsTestData( chunkEcsTestData[i].value + 100 ); + } + } + else if (chunkEcsTestData2.Length > 0) + { + for (int i = 0; i < chunkCount; i++) + { + chunkEcsTestData2[i] = new EcsTestData2( chunkEcsTestData2[i].value0 - 1000 ); + } + } + } + } + + [Test] + public void ACS_WriteMixed() + { + CreateMixedEntities(64); + + var chunks = m_Manager.CreateArchetypeChunkArray( + new ComponentType[] {typeof(EcsTestData2), typeof(EcsTestData)}, // any + Array.Empty(), // none + Array.Empty(), // all + Allocator.Temp); + + Assert.AreEqual(14,chunks.Length); + + var ecsTestData = m_Manager.GetArchetypeChunkComponentType(false); + var ecsTestData2 = m_Manager.GetArchetypeChunkComponentType(false); + var changeValuesJobs = new ChangeMixedValues + { + chunks = chunks, + ecsTestData = ecsTestData, + ecsTestData2 = ecsTestData2, + }; + + var collectValuesJobHandle = changeValuesJobs.Schedule(chunks.Length, 64); + collectValuesJobHandle.Complete(); + + ulong foundValues = 0; + for (int chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++) + { + var chunk = chunks[chunkIndex]; + var chunkCount = chunk.Count; + + Assert.AreEqual(4,math.ceil_pow2(chunkCount-1)); + + var chunkEcsTestData = chunk.GetNativeArray(ecsTestData); + var chunkEcsTestData2 = chunk.GetNativeArray(ecsTestData2); + if (chunkEcsTestData.Length > 0) + { + for (int i = 0; i < chunkCount; i++) + { + foundValues |= (ulong)1 << (chunkEcsTestData[i].value-100); + } + } + else if (chunkEcsTestData2.Length > 0) + { + for (int i = 0; i < chunkCount; i++) + { + foundValues |= (ulong)1 << (-chunkEcsTestData2[i].value0-1000); + } + } + } + + foundValues++; + Assert.AreEqual(0,foundValues); + + chunks.Dispose(); + } + + struct ChangeMixedValuesSharedFilter : IJobParallelFor + { + [ReadOnly] public ArchetypeChunkArray chunks; + public ArchetypeChunkComponentType ecsTestData; + public ArchetypeChunkComponentType ecsTestData2; + [ReadOnly] public ArchetypeChunkSharedComponentType ecsTestSharedData; + public int sharedFilterIndex; + + public void Execute(int chunkIndex) + { + var chunk = chunks[chunkIndex]; + var chunkCount = chunk.Count; + var chunkEcsTestData = chunk.GetNativeArray(ecsTestData); + var chunkEcsTestData2 = chunk.GetNativeArray(ecsTestData2); + var chunkEcsSharedDataIndex = chunk.GetSharedComponentIndex(ecsTestSharedData); + + if (chunkEcsSharedDataIndex != sharedFilterIndex) + return; + + if (chunkEcsTestData.Length > 0) + { + for (int i = 0; i < chunkCount; i++) + { + chunkEcsTestData[i] = new EcsTestData( chunkEcsTestData[i].value + 100 ); + } + } + else if (chunkEcsTestData2.Length > 0) + { + for (int i = 0; i < chunkCount; i++) + { + chunkEcsTestData2[i] = new EcsTestData2( chunkEcsTestData2[i].value0 - 1000 ); + } + } + } + } + + [Test] + public void ACS_WriteMixedFilterShared() + { + CreateMixedEntities(64); + + Assert.AreEqual(1,m_Manager.GlobalSystemVersion); + + // Only update shared value == 1 + var unique = new List(0); + m_Manager.GetAllUniqueSharedComponentDatas(unique); + var sharedFilterValue = 1; + var sharedFilterIndex = -1; + for (int i = 0; i < unique.Count; i++) + { + if (unique[i].value == sharedFilterValue) + { + sharedFilterIndex = i; + break; + } + } + + var chunks = m_Manager.CreateArchetypeChunkArray( + new ComponentType[] {typeof(EcsTestData2), typeof(EcsTestData)}, // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsTestSharedComp)}, // all + Allocator.Temp); + + Assert.AreEqual(14,chunks.Length); + + var ecsTestData = m_Manager.GetArchetypeChunkComponentType(false); + var ecsTestData2 = m_Manager.GetArchetypeChunkComponentType(false); + var ecsTestSharedData = m_Manager.GetArchetypeChunkSharedComponentType(true); + var changeValuesJobs = new ChangeMixedValuesSharedFilter + { + chunks = chunks, + ecsTestData = ecsTestData, + ecsTestData2 = ecsTestData2, + ecsTestSharedData = ecsTestSharedData, + sharedFilterIndex = sharedFilterIndex + }; + + var collectValuesJobHandle = changeValuesJobs.Schedule(chunks.Length, 64); + collectValuesJobHandle.Complete(); + + ulong foundValues = 0; + for (int chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++) + { + var chunk = chunks[chunkIndex]; + var chunkCount = chunk.Count; + + Assert.AreEqual(4,math.ceil_pow2(chunkCount-1)); + + var chunkEcsSharedDataIndex = chunk.GetSharedComponentIndex(ecsTestSharedData); + + var chunkEcsTestData = chunk.GetNativeArray(ecsTestData); + var chunkEcsTestData2 = chunk.GetNativeArray(ecsTestData2); + if (chunkEcsTestData.Length > 0) + { + var chunkEcsTestDataVersion = chunk.GetComponentVersion(ecsTestData); + + Assert.AreEqual(1, chunkEcsTestDataVersion); + + for (int i = 0; i < chunkCount; i++) + { + if (chunkEcsSharedDataIndex == sharedFilterIndex) + { + foundValues |= (ulong)1 << (chunkEcsTestData[i].value-100); + } + else + { + foundValues |= (ulong)1 << (chunkEcsTestData[i].value); + } + } + } + else if (chunkEcsTestData2.Length > 0) + { + var chunkEcsTestData2Version = chunk.GetComponentVersion(ecsTestData2); + + Assert.AreEqual(1, chunkEcsTestData2Version); + + for (int i = 0; i < chunkCount; i++) + { + if (chunkEcsSharedDataIndex == sharedFilterIndex) + { + foundValues |= (ulong)1 << (-chunkEcsTestData2[i].value0-1000); + } + else + { + foundValues |= (ulong)1 << (-chunkEcsTestData2[i].value0); + } + } + } + } + + foundValues++; + Assert.AreEqual(0,foundValues); + + chunks.Dispose(); + } + } +} diff --git a/Unity.Entities.Tests/ArchetypeChunkArrayTests.cs.meta b/Unity.Entities.Tests/ArchetypeChunkArrayTests.cs.meta new file mode 100644 index 00000000..463de751 --- /dev/null +++ b/Unity.Entities.Tests/ArchetypeChunkArrayTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a53f438f5b01b494fab465042f68aac8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/ComponentGroupArrayTests.cs b/Unity.Entities.Tests/ComponentGroupArrayTests.cs new file mode 100644 index 00000000..43216c23 --- /dev/null +++ b/Unity.Entities.Tests/ComponentGroupArrayTests.cs @@ -0,0 +1,117 @@ +using NUnit.Framework; +using Unity.Jobs; +using Unity.Collections; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class ComponentGroupArrayTests : ECSTestsFixture + { + public ComponentGroupArrayTests() + { + Assert.IsTrue(Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobDebuggerEnabled, "JobDebugger must be enabled for these tests"); + } + + struct TestCopy1To2Job : IJob + { + public ComponentGroupArray entities; + unsafe public void Execute() + { + foreach (var e in entities) + e.testData2->value0 = e.testData->value; + } + } + + struct TestReadOnlyJob : IJob + { + public ComponentGroupArray entities; + public void Execute() + { + foreach (var e in entities) + ; + } + } + + + //@TODO: Test for Entity setup with same component twice... + //@TODO: Test for subtractive components + //@TODO: Test for process ComponentGroupArray in job + + unsafe struct TestEntity + { + [ReadOnly] + public EcsTestData* testData; + public EcsTestData2* testData2; + } + + unsafe struct TestEntityReadOnly + { + [ReadOnly] + public EcsTestData* testData; + [ReadOnly] + public EcsTestData2* testData2; + } + + [Test] + public void ComponentAccessAfterScheduledJobThrowsEntityArray() + { + m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var job = new TestCopy1To2Job(); + job.entities = EmptySystem.GetEntities(); + + var fence = job.Schedule(); + + var entityArray = EmptySystem.GetEntities(); + Assert.Throws(() => { var temp = entityArray[0]; }); + + fence.Complete(); + } + + [Test] + public void ComponentGroupArrayJobScheduleDetectsWriteDependency() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + + var job = new TestCopy1To2Job(); + job.entities = EmptySystem.GetEntities(); + + var fence = job.Schedule(); + Assert.Throws(() => { job.Schedule(); }); + + fence.Complete(); + } + + [Test] + public void ComponentGroupArrayJobScheduleReadOnlyParallelIsAllowed() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + + var job = new TestReadOnlyJob(); + job.entities = EmptySystem.GetEntities(); + + var fence = job.Schedule(); + var fence2 = job.Schedule(); + + JobHandle.CompleteAll(ref fence, ref fence2); + } + + unsafe struct TestEntitySub2 + { + public EcsTestData* testData; + public SubtractiveComponent testData2; + } + + [Test] + public void ComponentGroupArraySubtractive() + { + m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + m_Manager.CreateEntity(typeof(EcsTestData)); + + var entities = EmptySystem.GetEntities(); + Assert.AreEqual(1, entities.Length); + } + } +} \ No newline at end of file diff --git a/Unity.Entities.Tests/ComponentGroupArrayTests.cs.meta b/Unity.Entities.Tests/ComponentGroupArrayTests.cs.meta new file mode 100644 index 00000000..8e6b5fce --- /dev/null +++ b/Unity.Entities.Tests/ComponentGroupArrayTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 41659abe70864cfdbd0d142215cf0cd9 +timeCreated: 1512497553 \ No newline at end of file diff --git a/Unity.Entities.Tests/ComponentGroupDelta.cs b/Unity.Entities.Tests/ComponentGroupDelta.cs new file mode 100644 index 00000000..17aaeaae --- /dev/null +++ b/Unity.Entities.Tests/ComponentGroupDelta.cs @@ -0,0 +1,670 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using Unity.Jobs; +using UnityEditor; + +namespace Unity.Entities.Tests +{ + [TestFixture] + public class ComponentGroupIndexFilter : ECSTestsFixture + { + public Entity CreateEntity(int value, int sharedValue) + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestSharedComp)); + m_Manager.SetComponentData(entity, new EcsTestData(value)); + m_Manager.SetSharedComponentData(entity, new EcsTestSharedComp(sharedValue)); + return entity; + } + + void CreateEntities(int count) + { + for (int i = 0; i != count; i++) + CreateEntity(i, i / 7); + } + + void CheckFilter(NativeArray indexList) + { + var group = EmptySystem.GetComponentGroup(typeof(EcsTestData)); + group.SetFilter(indexList); + + var array = group.GetComponentDataArray(); + + for (int i = 0; i != indexList.Length; i++) + Assert.AreEqual(indexList[i], array[i].value); + } + + [Test] + public void IterateFiltered() + { + CreateEntities(100); + + var list = new NativeArray(10, Allocator.Temp); + for (int i = 0; i != 10; i++) + list[i] = 10 * i; + + CheckFilter(list); + + list.Dispose(); + } + + [Test] + public void Consecutive() + { + CreateEntities(100); + + var list = new NativeArray(99, Allocator.Temp); + for (int i = 0; i != 99; i++) + list[i] = i + 1; + + CheckFilter(list); + + list.Dispose(); + } + + [Test] + public void IterateStress() + { + UnityEngine.Random.InitState(0); + int entityCount = 100; + CreateEntities(100); + + var list = new NativeArray(200, Allocator.Temp); + for (int i = 0; i != list.Length;) + { + int count = UnityEngine.Random.Range(1, 29); + if (count + i > list.Length) + count = list.Length - i; + int baseIndex = UnityEngine.Random.Range(0, entityCount - count); + + for (int j = 0; j < count; j++) + list[j + i] = baseIndex + j; + + i += count; + } + + CheckFilter(list); + + list.Dispose(); + } + + [Test] + [Ignore("Needs to be implemented")] + public void IndexListSafety() + { + throw new System.NotImplementedException(); + + // * Destroy / mutate index list while job is running + // * Index out of bounds for IndexList... + + } + } + + [TestFixture] + public class ComponentGroupDelta : ECSTestsFixture + { + // * TODO: using out of date version cached ComponentDataArray should give exception... (We store the System order version in it...) + // * TODO: Using monobehaviour as delta inputs? + // * TODO: Self-delta-mutation doesn't trigger update (ComponentDataFromEntity.cs) + // /////@TODO: GlobalSystemVersion can't be pulled from m_Entities... indeterministic + // * TODO: Chained delta works + // How can this work? Need to use specialized job type because the number of entities to be + // processed isn't known until running the job... Need some kind of late binding of parallel for length etc... + // How do we prevent incorrect usage / default... + + [DisableAutoCreation] + public class DeltaCheckSystem : ComponentSystem + { + public Entity[] Expected; + + protected override void OnUpdate() + { + var group = GetComponentGroup(typeof(EcsTestData)); + group.SetFilterChanged(typeof(EcsTestData)); + + CollectionAssert.AreEqual(Expected, group.GetEntityArray().ToArray()); + } + + public void UpdateExpect(Entity[] expected) + { + Expected = expected; + Update(); + } + } + + + [Test] + public void CreateEntityDoesNotTriggerChange() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + var deltaCheckSystem = World.CreateManager(); + deltaCheckSystem.UpdateExpect(new Entity[0]); + } + + public enum ChangeMode + { + SetComponentData, + SetComponentDataFromEntity, + ComponentDataArray, + ComponentGroupArray + } + + unsafe struct GroupRW + { + public EcsTestData* Data; + } + + unsafe struct GroupRO + { + [ReadOnly] + public EcsTestData* Data; + } + + + unsafe void SetValue(int index, int value, ChangeMode mode) + { + EmptySystem.Update(); + var entityArray = EmptySystem.GetComponentGroup(typeof(EcsTestData)).GetEntityArray(); + var entity = entityArray[index]; + + if (mode == ChangeMode.ComponentDataArray) + { + var array = EmptySystem.GetComponentGroup(typeof(EcsTestData)).GetComponentDataArray(); + array[index] = new EcsTestData(value); + } + else if (mode == ChangeMode.SetComponentData) + { + m_Manager.SetComponentData(entity, new EcsTestData(value)); + } + else if (mode == ChangeMode.SetComponentDataFromEntity) + { + //@TODO: Chaining correctness... Definately not implemented right now... + var array = EmptySystem.GetComponentDataFromEntity(false); + array[entity] = new EcsTestData(value); + } + else if (mode == ChangeMode.ComponentGroupArray) + { + *(EmptySystem.GetEntities()[index].Data) = new EcsTestData(value); + } + } + + void GetValue(ChangeMode mode) + { + EmptySystem.Update(); + var entityArray = EmptySystem.GetComponentGroup(typeof(EcsTestData)).GetEntityArray(); + + if (mode == ChangeMode.ComponentDataArray) + { + var array = EmptySystem.GetComponentGroup(typeof(EcsTestData)).GetComponentDataArray(); + for (int i = 0; i != array.Length; i++) + { + var val = array[i]; + } + } + else if (mode == ChangeMode.SetComponentData) + { + for(int i = 0;i != entityArray.Length;i++) + m_Manager.GetComponentData(entityArray[i]); + } + else if (mode == ChangeMode.SetComponentDataFromEntity) + { + var array = EmptySystem.GetComponentDataFromEntity(false); + for(int i = 0;i != entityArray.Length;i++) + m_Manager.GetComponentData(entityArray[i]); + } + else if (mode == ChangeMode.ComponentGroupArray) + { + foreach (var e in EmptySystem.GetEntities()) + ; + } + } + + [Theory] + public void ChangeEntity(ChangeMode mode) + { + var entity0 = m_Manager.CreateEntity(typeof(EcsTestData)); + var entity1 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var deltaCheckSystem0 = World.CreateManager(); + var deltaCheckSystem1 = World.CreateManager(); + + SetValue(0, 2, mode); + + deltaCheckSystem0.UpdateExpect(new Entity[] { entity0 }); + + SetValue(1, 2, mode); + + deltaCheckSystem0.UpdateExpect(new Entity[] { entity1 }); + deltaCheckSystem1.UpdateExpect(new Entity[] { entity0, entity1 }); + + deltaCheckSystem0.UpdateExpect(new Entity[0]); + deltaCheckSystem1.UpdateExpect(new Entity[0]); + } + + [Theory] + public void GetEntityDataDoesNotChange(ChangeMode mode) + { + m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var deltaCheckSystem = World.CreateManager(); + GetValue(mode); + deltaCheckSystem.UpdateExpect(new Entity[] { }); + } + + [Test] + public void ChangeEntityWrap() + { + m_Manager.Debug.SetGlobalSystemVersion(uint.MaxValue-3); + + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + + var deltaCheckSystem = World.CreateManager(); + + for (int i = 0; i != 7; i++) + { + SetValue(0, 1, ChangeMode.SetComponentData); + deltaCheckSystem.UpdateExpect(new Entity[] { entity }); + } + + deltaCheckSystem.UpdateExpect(new Entity[0]); + } + + [Test] + public void NoChangeEntityWrap() + { + m_Manager.Debug.SetGlobalSystemVersion(uint.MaxValue - 3); + + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + SetValue(0, 2, ChangeMode.SetComponentData); + + var deltaCheckSystem = World.CreateManager(); + deltaCheckSystem.UpdateExpect(new Entity[] { entity }); + + for (int i = 0; i != 7; i++) + deltaCheckSystem.UpdateExpect(new Entity[0]); + } + + [DisableAutoCreation] + public class DeltaProcessComponentSystem : JobComponentSystem + { + struct DeltaJob : IJobProcessComponentData + { + public void Execute([ChangedFilter][ReadOnly]ref EcsTestData input, ref EcsTestData2 output) + { + output.value0 += input.value + 100; + } + } + + protected override JobHandle OnUpdate(JobHandle deps) + { + return new DeltaJob().Schedule(this, 1, deps); + } + } + + + [Test] + public void IJobProcessComponentDeltaWorks() + { + var entity0 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2), typeof(EcsTestData3)); + var entity1 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var deltaSystem = World.CreateManager(); + + SetValue(0, 2, ChangeMode.SetComponentData); + + deltaSystem.Update(); + + Assert.AreEqual(100 + 2, m_Manager.GetComponentData(entity0).value0); + Assert.AreEqual(0, m_Manager.GetComponentData(entity1).value0); + } + +#if false + [Test] + public void IJobProcessComponentDeltaWorksWhenSetSharedComponent() + { + var entity0 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2), typeof(EcsTestData3), typeof(EcsTestSharedComp)); + var entity1 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var deltaSystem = World.CreateManager(); + + SetValue(0, 2, ChangeMode.SetComponentData); + m_Manager.SetSharedComponentData(entity0,new EcsTestSharedComp(50)); + + deltaSystem.Update(); + + Assert.AreEqual(100 + 2, m_Manager.GetComponentData(entity0).value0); + Assert.AreEqual(0, m_Manager.GetComponentData(entity1).value0); + } +#endif + + [DisableAutoCreation] + public class ModifyComponentSystem1Comp : JobComponentSystem + { + public ComponentGroup m_Group; + public EcsTestSharedComp m_sharedComp; + + struct DeltaJob : IJobParallelFor + { + public ComponentDataArray data; + + public void Execute(int index) + { + data[index] = new EcsTestData(100); + } + } + + protected override JobHandle OnUpdate(JobHandle deps) + { + m_Group = GetComponentGroup( + typeof(EcsTestData), + ComponentType.ReadOnly(typeof(EcsTestSharedComp))); + + m_Group.SetFilter(m_sharedComp); + + DeltaJob job = new DeltaJob(); + job.data = m_Group.GetComponentDataArray(); + return job.Schedule(job.data.Length, 1, deps); + } + } + + [DisableAutoCreation] + public class DeltaModifyComponentSystem1Comp : JobComponentSystem + { + struct DeltaJob : IJobProcessComponentData + { + public void Execute([ChangedFilter]ref EcsTestData output) + { + output.value += 150; + } + } + + protected override JobHandle OnUpdate(JobHandle deps) + { + return new DeltaJob().Schedule(this, 1, deps); + } + } + + [Test] + public void ChangedFilterJobAfterAnotherJob1Comp() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestSharedComp)); + var entities = new NativeArray(10000, Allocator.Persistent); + m_Manager.CreateEntity(archetype, entities); + + var modifSystem = World.CreateManager(); + var deltaSystem = World.CreateManager(); + + modifSystem.m_sharedComp = new EcsTestSharedComp(456); + for (int i = 123; i < entities.Length; i += 345) + { + m_Manager.SetSharedComponentData(entities[i], modifSystem.m_sharedComp); + } + + modifSystem.Update(); + deltaSystem.Update(); + + foreach (var entity in entities) + { + if (m_Manager.GetSharedComponentData(entity).value == 456) + { + Assert.AreEqual(250, m_Manager.GetComponentData(entity).value); + } + else + { + Assert.AreEqual(0, m_Manager.GetComponentData(entity).value); + } + } + + entities.Dispose(); + } + + [DisableAutoCreation] + public class ModifyComponentSystem2Comp : JobComponentSystem + { + public ComponentGroup m_Group; + public EcsTestSharedComp m_sharedComp; + + struct DeltaJob : IJobParallelFor + { + public ComponentDataArray data; + public ComponentDataArray data2; + + public void Execute(int index) + { + data[index] = new EcsTestData(100); + data2[index] = new EcsTestData2(102); + } + } + + protected override JobHandle OnUpdate(JobHandle deps) + { + m_Group = GetComponentGroup( + typeof(EcsTestData), + typeof(EcsTestData2), + ComponentType.ReadOnly(typeof(EcsTestSharedComp))); + + m_Group.SetFilter(m_sharedComp); + + DeltaJob job = new DeltaJob(); + job.data = m_Group.GetComponentDataArray(); + job.data2 = m_Group.GetComponentDataArray(); + return job.Schedule(job.data.Length, 1, deps); + } + } + + [DisableAutoCreation] + public class DeltaModifyComponentSystem2Comp : JobComponentSystem + { + struct DeltaJobChanged0 : IJobProcessComponentData + { + public void Execute([ChangedFilter]ref EcsTestData output, ref EcsTestData2 output2) + { + output.value += 150; + output2.value0 += 152; + } + } + + struct DeltaJobChanged1 : IJobProcessComponentData + { + public void Execute(ref EcsTestData output, [ChangedFilter]ref EcsTestData2 output2) + { + output.value += 150; + output2.value0 += 152; + } + } + + public enum Variant + { + FirstComponentChanged, + SecondComponentChanged, + } + + public Variant variant; + + protected override JobHandle OnUpdate(JobHandle deps) + { + switch (variant) + { + case Variant.FirstComponentChanged: + return new DeltaJobChanged0().Schedule(this, 1, deps); + case Variant.SecondComponentChanged: + return new DeltaJobChanged1().Schedule(this, 1, deps); + } + + throw new NotImplementedException(); + } + } + + [Theory] + public void ChangedFilterJobAfterAnotherJob2Comp(DeltaModifyComponentSystem2Comp.Variant variant) + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2), typeof(EcsTestSharedComp)); + var entities = new NativeArray(10000, Allocator.Persistent); + m_Manager.CreateEntity(archetype, entities); + + var modifSystem = World.CreateManager(); + var deltaSystem = World.CreateManager(); + + deltaSystem.variant = variant; + + modifSystem.m_sharedComp = new EcsTestSharedComp(456); + for (int i = 123; i < entities.Length; i += 345) + { + m_Manager.SetSharedComponentData(entities[i], modifSystem.m_sharedComp); + } + + modifSystem.Update(); + deltaSystem.Update(); + + foreach (var entity in entities) + { + if (m_Manager.GetSharedComponentData(entity).value == 456) + { + Assert.AreEqual(250, m_Manager.GetComponentData(entity).value); + Assert.AreEqual(254, m_Manager.GetComponentData(entity).value0); + } + else + { + Assert.AreEqual(0, m_Manager.GetComponentData(entity).value); + } + } + + entities.Dispose(); + } + + [DisableAutoCreation] + public class ModifyComponentSystem3Comp : JobComponentSystem + { + public ComponentGroup m_Group; + public EcsTestSharedComp m_sharedComp; + + struct DeltaJob : IJobParallelFor + { + public ComponentDataArray data; + public ComponentDataArray data2; + public ComponentDataArray data3; + + public void Execute(int index) + { + data[index] = new EcsTestData(100); + data2[index] = new EcsTestData2(102); + data3[index] = new EcsTestData3(103); + } + } + + protected override JobHandle OnUpdate(JobHandle deps) + { + m_Group = GetComponentGroup( + typeof(EcsTestData), + typeof(EcsTestData2), + typeof(EcsTestData3), + ComponentType.ReadOnly(typeof(EcsTestSharedComp))); + + m_Group.SetFilter(m_sharedComp); + + DeltaJob job = new DeltaJob(); + job.data = m_Group.GetComponentDataArray(); + job.data2 = m_Group.GetComponentDataArray(); + job.data3 = m_Group.GetComponentDataArray(); + return job.Schedule(job.data.Length, 1, deps); + } + } + + [DisableAutoCreation] + public class DeltaModifyComponentSystem3Comp : JobComponentSystem + { + struct DeltaJobChanged0 : IJobProcessComponentData + { + public void Execute([ChangedFilter]ref EcsTestData output, ref EcsTestData2 output2, ref EcsTestData3 output3) + { + output.value += 150; + output2.value0 += 152; + output3.value0 += 153; + } + } + + struct DeltaJobChanged1 : IJobProcessComponentData + { + public void Execute(ref EcsTestData output, [ChangedFilter]ref EcsTestData2 output2, ref EcsTestData3 output3) + { + output.value += 150; + output2.value0 += 152; + output3.value0 += 153; + } + } + + struct DeltaJobChanged2 : IJobProcessComponentData + { + public void Execute(ref EcsTestData output, ref EcsTestData2 output2, [ChangedFilter]ref EcsTestData3 output3) + { + output.value += 150; + output2.value0 += 152; + output3.value0 += 153; + } + } + + public enum Variant + { + FirstComponentChanged, + SecondComponentChanged, + ThirdComponentChanged, + } + + public Variant variant; + + protected override JobHandle OnUpdate(JobHandle deps) + { + switch (variant) + { + case Variant.FirstComponentChanged: + return new DeltaJobChanged0().Schedule(this, 1, deps); + case Variant.SecondComponentChanged: + return new DeltaJobChanged1().Schedule(this, 1, deps); + case Variant.ThirdComponentChanged: + return new DeltaJobChanged2().Schedule(this, 1, deps); + } + + throw new NotImplementedException(); + } + } + + [Theory] + public void ChangedFilterJobAfterAnotherJob3Comp(DeltaModifyComponentSystem3Comp.Variant variant) + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2), typeof(EcsTestData3), typeof(EcsTestSharedComp)); + var entities = new NativeArray(10000, Allocator.Persistent); + m_Manager.CreateEntity(archetype, entities); + + var modifSystem = World.CreateManager(); + var deltaSystem = World.CreateManager(); + + deltaSystem.variant = variant; + + modifSystem.m_sharedComp = new EcsTestSharedComp(456); + for (int i = 123; i < entities.Length; i += 345) + { + m_Manager.SetSharedComponentData(entities[i], modifSystem.m_sharedComp); + } + + modifSystem.Update(); + deltaSystem.Update(); + + foreach (var entity in entities) + { + if (m_Manager.GetSharedComponentData(entity).value == 456) + { + Assert.AreEqual(250, m_Manager.GetComponentData(entity).value); + Assert.AreEqual(254, m_Manager.GetComponentData(entity).value0); + Assert.AreEqual(256, m_Manager.GetComponentData(entity).value0); + } + else + { + Assert.AreEqual(0, m_Manager.GetComponentData(entity).value); + } + } + + entities.Dispose(); + } + } +} diff --git a/Unity.Entities.Tests/ComponentGroupDelta.cs.meta b/Unity.Entities.Tests/ComponentGroupDelta.cs.meta new file mode 100644 index 00000000..17cc5be7 --- /dev/null +++ b/Unity.Entities.Tests/ComponentGroupDelta.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 73b90709efa7c4d11b5f436b305c5d50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/ComponentOrderVersionTests.cs b/Unity.Entities.Tests/ComponentOrderVersionTests.cs new file mode 100644 index 00000000..b8bc001a --- /dev/null +++ b/Unity.Entities.Tests/ComponentOrderVersionTests.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class ComponentOrderVersionTests : ECSTestsFixture + { + int oddTestValue = 34; + int evenTestValue = 17; + + void AddEvenOddTestData() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData)); + var evenShared = new SharedData1(evenTestValue); + var oddShared = new SharedData1(oddTestValue); + for (int i = 0; i < 100; i++) + { + Entity e = m_Manager.CreateEntity(archetype); + var testData = m_Manager.GetComponentData(e); + testData.value = i; + m_Manager.SetComponentData(e, testData); + if ((i & 0x01) == 0) + { + m_Manager.AddSharedComponentData(e, evenShared); + } + else + { + m_Manager.AddSharedComponentData(e, oddShared); + } + } + } + + void ActionEvenOdd(Action even, Action odd) + { + var uniqueTypes = new List(10); + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData1)); + group.CompleteDependency(); + + m_Manager.GetAllUniqueSharedComponentDatas(uniqueTypes); + + for (int sharedIndex = 0; sharedIndex != uniqueTypes.Count; sharedIndex++) + { + var sharedData = uniqueTypes[sharedIndex]; + group.SetFilter(sharedData); + var version = m_Manager.GetSharedComponentOrderVersion(sharedData); + + if (sharedData.value == evenTestValue) + { + even(version, group); + } + + if (sharedData.value == oddTestValue) + { + odd(version, group); + } + } + + group.Dispose(); + } + + [Test] + public void SharedComponentNoChangeVersionUnchanged() + { + AddEvenOddTestData(); + ActionEvenOdd((version, group) => { Assert.AreEqual(version, 1); }, + (version, group) => { Assert.AreEqual(version, 1); }); + } + + void TestSourceEvenValues(int version, ComponentGroup group) + { + var testData = group.GetComponentDataArray(); + + Assert.AreEqual(50, testData.Length); + + for (int i = 0; i < 50; i++) + { + Assert.AreEqual(i * 2, testData[i].value); + } + } + + void TestSourceOddValues(int version, ComponentGroup group) + { + var testData = group.GetComponentDataArray(); + + Assert.AreEqual(50, testData.Length); + + for (int i = 0; i < 50; i++) + { + Assert.AreEqual(1 + (i * 2), testData[i].value); + } + } + + [Test] + public void SharedComponentNoChangeValuesUnchanged() + { + AddEvenOddTestData(); + ActionEvenOdd(TestSourceEvenValues, TestSourceOddValues); + } + + void ChangeGroupOrder(int version, ComponentGroup group) + { + var entityData = group.GetEntityArray(); + var entities = new NativeArray(50, Allocator.Temp); + entityData.CopyTo(new NativeSlice(entities)); + + for (int i = 0; i < 50; i++) + { + var e = entities[i]; + if ((i & 0x01) == 0) + { + var testData2 = new EcsTestData2(i); + m_Manager.AddComponentData(e, testData2); + } + } + + entities.Dispose(); + } + + [Test] + public void SharedComponentChangeOddGroupOrderOnlyOddVersionChanged() + { + AddEvenOddTestData(); + + ActionEvenOdd((version, group) => { }, ChangeGroupOrder); + ActionEvenOdd((version, group) => { Assert.AreEqual(version, 1); }, + (version, group) => { Assert.Greater(version, 1); }); + } + + [Test] + public void SharedComponentChangeOddGroupOrderEvenValuesUnchanged() + { + AddEvenOddTestData(); + + ActionEvenOdd((version, group) => { }, ChangeGroupOrder); + ActionEvenOdd(TestSourceEvenValues, (version, group) => { }); + } + + void DestroyAllButOneEntityInGroup(int version, ComponentGroup group) + { + var entityData = group.GetEntityArray(); + var entities = new NativeArray(50, Allocator.Temp); + entityData.CopyTo(new NativeSlice(entities)); + + for (int i = 0; i < 49; i++) + { + var e = entities[i]; + m_Manager.DestroyEntity(e); + } + + entities.Dispose(); + } + + [Test] + public void SharedComponentDestroyAllButOneEntityInOddGroupOnlyOddVersionChanged() + { + AddEvenOddTestData(); + + ActionEvenOdd((version, group) => { }, DestroyAllButOneEntityInGroup); + ActionEvenOdd((version, group) => { Assert.AreEqual(version, 1); }, + (version, group) => { Assert.Greater(version, 1); }); + } + + [Test] + public void SharedComponentDestroyAllButOneEntityInOddGroupEvenValuesUnchanged() + { + AddEvenOddTestData(); + + ActionEvenOdd((version, group) => { }, DestroyAllButOneEntityInGroup); + ActionEvenOdd(TestSourceEvenValues, (version, group) => { }); + } + + [Test] + public void CreateEntity() + { + m_Manager.CreateEntity(typeof(EcsTestData)); + Assert.AreEqual(1, m_Manager.GetComponentOrderVersion()); + Assert.AreEqual(0, m_Manager.GetComponentOrderVersion()); + } + + [Test] + public void DestroyEntity() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.DestroyEntity(entity); + + Assert.AreEqual(2, m_Manager.GetComponentOrderVersion()); + Assert.AreEqual(0, m_Manager.GetComponentOrderVersion()); + } + + [Test] + public void AddComponent() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.AddComponentData(entity, new EcsTestData2()); + + Assert.AreEqual(2, m_Manager.GetComponentOrderVersion()); + Assert.AreEqual(1, m_Manager.GetComponentOrderVersion()); + } + + [Test] + public void RemoveComponent() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + m_Manager.RemoveComponent(entity); + + Assert.AreEqual(2, m_Manager.GetComponentOrderVersion()); + Assert.AreEqual(2, m_Manager.GetComponentOrderVersion()); + } + + [Test] + public void SetSharedComponent() + { + var entity = m_Manager.CreateEntity(typeof(SharedData1), typeof(SharedData2)); + m_Manager.SetSharedComponentData(entity, new SharedData1(1)); + + Assert.AreEqual(2, m_Manager.GetComponentOrderVersion()); + Assert.AreEqual(2, m_Manager.GetComponentOrderVersion()); + + Assert.AreEqual(1, m_Manager.GetSharedComponentOrderVersion(new SharedData1(1))); + } + + [Test] + public void DestroySharedComponentEntity() + { + var sharedData = new SharedData1(1); + + var destroyEntity = m_Manager.CreateEntity(typeof(SharedData1)); + m_Manager.SetSharedComponentData(destroyEntity, sharedData ); + /*var dontDestroyEntity = */ m_Manager.Instantiate(destroyEntity); + + Assert.AreEqual(2, m_Manager.GetSharedComponentOrderVersion(sharedData)); + + m_Manager.DestroyEntity(destroyEntity); + + Assert.AreEqual(3, m_Manager.GetSharedComponentOrderVersion(sharedData)); + } + + [Test] + public void DestroySharedComponentDataSetsOrderVersionToZero() + { + var sharedData = new SharedData1(1); + var entity = m_Manager.CreateEntity(); + m_Manager.AddSharedComponentData(entity, sharedData); + + m_Manager.DestroyEntity(entity); + + Assert.AreEqual(0, m_Manager.GetSharedComponentOrderVersion(sharedData)); + } + } +} diff --git a/Unity.Entities.Tests/ComponentOrderVersionTests.cs.meta b/Unity.Entities.Tests/ComponentOrderVersionTests.cs.meta new file mode 100644 index 00000000..9404d804 --- /dev/null +++ b/Unity.Entities.Tests/ComponentOrderVersionTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c34323cfa31f54c249ec49a7d61bbce4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/ComponentSystemInjectionTests.cs b/Unity.Entities.Tests/ComponentSystemInjectionTests.cs new file mode 100644 index 00000000..e5f36e1c --- /dev/null +++ b/Unity.Entities.Tests/ComponentSystemInjectionTests.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class ComponentSystemInjectionTests : ECSTestsFixture + { + [DisableAutoCreation] + class TestSystem : ComponentSystem + { + protected override void OnUpdate() + { + } + } + + [DisableAutoCreation] + class AttributeInjectionSystem : ComponentSystem + { + [Inject] + public TestSystem test; + + protected override void OnUpdate() + { + } + } + + [DisableAutoCreation] + class ConstructorInjectionSystem : ComponentSystem + { + public string test; + + public ConstructorInjectionSystem(string value) + { + this.test = value; + } + + protected override void OnUpdate() + { + } + } + + [Test] + public void ConstructorInjection() + { + var hello = "HelloWorld"; + var system = World.CreateManager(hello); + Assert.AreEqual(hello, system.test); + } + + [Test] + public void AttributeInjectionCreates() + { + var system = World.CreateManager(); + Assert.AreEqual(World.GetOrCreateManager(), system.test); + } + + [Test] + public void AttributeInjectionAlreadyCreated() + { + var test = World.CreateManager(); + var system = World.CreateManager(); + Assert.AreEqual(test, system.test); + } + } +} \ No newline at end of file diff --git a/Unity.Entities.Tests/ComponentSystemInjectionTests.cs.meta b/Unity.Entities.Tests/ComponentSystemInjectionTests.cs.meta new file mode 100644 index 00000000..ab977e91 --- /dev/null +++ b/Unity.Entities.Tests/ComponentSystemInjectionTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 83110af8b51c435eb9e2d0a50d4ae5de +timeCreated: 1512086766 \ No newline at end of file diff --git a/Unity.Entities.Tests/ComponentSystemStartStopRunningTests.cs b/Unity.Entities.Tests/ComponentSystemStartStopRunningTests.cs new file mode 100644 index 00000000..e02dbf38 --- /dev/null +++ b/Unity.Entities.Tests/ComponentSystemStartStopRunningTests.cs @@ -0,0 +1,321 @@ +using NUnit.Framework; +using Unity.Collections; +using UnityEngine; +using UnityEngine.Experimental.PlayerLoop; +using UnityEngine.TestTools; + +namespace Unity.Entities.Tests +{ + public class ComponentSystemStartStopRunningTests : ECSTestsFixture + { + [DisableAutoCreation] + class TestSystem : ComponentSystem + { + public const string OnStartRunningString = + nameof(TestSystem) + ".OnStartRunning()"; + + public const string OnStopRunningString = + nameof(TestSystem) + ".OnStopRunning()"; + + struct MyStruct + { + public int Length; + public readonly ComponentDataArray Data; + } + + [Inject] + MyStruct DataStruct; + public NativeArray StoredData; + protected override void OnUpdate() + { + var index = StoredData[0] + DataStruct.Data[0].value + 1; + StoredData.Dispose(); + + StoredData = new NativeArray(1, Allocator.Temp); + StoredData[0] = index; + } + + protected override void OnStartRunning() + { + UnityEngine.Debug.Log(OnStartRunningString); + StoredData = new NativeArray(1, Allocator.Temp); + base.OnStartRunning(); + } + + protected override void OnStopRunning() + { + UnityEngine.Debug.Log(OnStopRunningString); + StoredData.Dispose(); + base.OnStopRunning(); + } + } + + TestSystem system; + Entity runSystemEntity = Entity.Null; + + public void ShouldRunSystem(bool shouldRun) + { + if (runSystemEntity != Entity.Null) + { + m_Manager.DestroyEntity(runSystemEntity); + runSystemEntity = Entity.Null; + } + + if (shouldRun) + { + runSystemEntity = m_Manager.CreateEntity(typeof(EcsTestData)); + } + } + + public override void Setup() + { + base.Setup(); + system = World.Active.GetOrCreateManager(); + ShouldRunSystem(true); + } + + public override void TearDown() + { + if (runSystemEntity != Entity.Null) + { + m_Manager.DestroyEntity(runSystemEntity); + runSystemEntity = Entity.Null; + } + if (system != null) + { + World.Active.DestroyManager(system); + system = null; + } + + base.TearDown(); + } + + [Test] + public void TempAllocation_DisposedInOnStopRunning_IsDisposed() + { + system.Update(); + + system.Enabled = false; + + system.Update(); + + Assert.IsFalse(system.StoredData.IsCreated); + } + + + [Test] + public void OnStartRunning_FirstUpdate_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStartRunning_WhenReEnabled_CalledOnce() + { + system.Enabled = false; + + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Enabled = true; + + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStartRunning_WithEnabledAndShouldRun_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Enabled = true; + ShouldRunSystem(true); + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStartRunning_WithDisabledAndShouldRun_NotCalled() + { + system.Enabled = false; + ShouldRunSystem(true); + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStartRunning_WithEnabledAndShouldNotRun_NotCalled() + { + system.Enabled = true; + ShouldRunSystem(false); + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStartRunning_WithDisabledAndShouldNotRun_NotCalled() + { + system.Enabled = false; + ShouldRunSystem(false); + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStartRunning_EnablingWhenShouldRunSystemIsTrue_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + + ShouldRunSystem(true); + system.Enabled = false; + system.Update(); + + system.Enabled = true; + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStartRunning_WhenShouldRunSystemBecomesTrue_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + ShouldRunSystem(true); + system.Enabled = true; + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStopRunningString); + ShouldRunSystem(false); + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + ShouldRunSystem(true); + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_WithEnabledAndShouldRun_NotCalled() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Update(); + + system.Enabled = true; + ShouldRunSystem(true); + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_WithDisabledAndShouldRun_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStopRunningString); + system.Enabled = false; + ShouldRunSystem(true); + system.Update(); + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_WithEnabledAndShouldNotRun_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStopRunningString); + system.Enabled = true; + ShouldRunSystem(false); + system.Update(); + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_WithDisabledAndShouldNotRun_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStopRunningString); + system.Enabled = false; + ShouldRunSystem(false); + system.Update(); + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_WhenDisabledBeforeFirstUpdate_NotCalled() + { + system.Enabled = false; + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_WhenDestroyingActiveManager_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStopRunningString); + World.Active.DestroyManager(system); + system = null; + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_WhenDestroyingInactiveManager_NotCalled() + { + system.Enabled = false; + system.Update(); + + World.Active.DestroyManager(system); + system = null; + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_WhenShouldRunSystemBecomesFalse_CalledOnce() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStopRunningString); + system.Enabled = false; + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + [Test] + public void OnStopRunning_DisablingWhenShouldRunSystemIsFalse_NotCalled() + { + LogAssert.Expect(LogType.Log, TestSystem.OnStartRunningString); + system.Update(); + + LogAssert.Expect(LogType.Log, TestSystem.OnStopRunningString); + ShouldRunSystem(false); + system.Update(); + + system.Enabled = false; + system.Update(); + + LogAssert.NoUnexpectedReceived(); + } + + } +} diff --git a/Unity.Entities.Tests/ComponentSystemStartStopRunningTests.cs.meta b/Unity.Entities.Tests/ComponentSystemStartStopRunningTests.cs.meta new file mode 100644 index 00000000..a7d18e5a --- /dev/null +++ b/Unity.Entities.Tests/ComponentSystemStartStopRunningTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3d3c169e5bed141d1884f7c86f6855bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/ComponentSystemTests.cs b/Unity.Entities.Tests/ComponentSystemTests.cs new file mode 100644 index 00000000..7d67b591 --- /dev/null +++ b/Unity.Entities.Tests/ComponentSystemTests.cs @@ -0,0 +1,222 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Unity.Entities.Tests +{ + public class ComponentSystemTests : ECSTestsFixture + { + [DisableAutoCreation] + class TestSystem : ComponentSystem + { + public bool Created = false; + + protected override void OnUpdate() + { + } + + protected override void OnCreateManager(int capacity) + { + Created = true; + } + + protected override void OnDestroyManager() + { + Created = false; + } + } + + [DisableAutoCreation] + class DerivedTestSystem : TestSystem + { + protected override void OnUpdate() + { + } + } + + [DisableAutoCreation] + class ThrowExceptionSystem : TestSystem + { + protected override void OnCreateManager(int capacity) + { + throw new System.Exception(); + } + protected override void OnUpdate() + { + } + } + + [DisableAutoCreation] + class ScheduleJobAndDestroyArray : JobComponentSystem + { + NativeArray test = new NativeArray(10, Allocator.Persistent); + + struct Job : IJob + { + public NativeArray test; + + public void Execute() { } + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + return new Job(){ test = test }.Schedule(inputDeps); + } + + protected override void OnDestroyManager() + { + // We expect this to not throw an exception since the jobs scheduled + // by this system should be synced before the system is destroyed + test.Dispose(); + } + } + + [Test] + public void Create() + { + var system = World.CreateManager(); + Assert.AreEqual(system, World.GetExistingManager()); + Assert.IsTrue(system.Created); + } + + [Test] + public void CreateAndDestroy() + { + var system = World.CreateManager(); + World.DestroyManager(system); + Assert.AreEqual(null, World.GetExistingManager()); + Assert.IsFalse(system.Created); + } + + [Test] + public void GetOrCreateManagerReturnsSameSystem() + { + var system = World.GetOrCreateManager(); + Assert.AreEqual(system, World.GetOrCreateManager()); + } + + [Test] + public void InheritedSystem() + { + var system = World.CreateManager(); + Assert.AreEqual(system, World.GetExistingManager()); + Assert.AreEqual(system, World.GetExistingManager()); + + World.DestroyManager(system); + + Assert.AreEqual(null, World.GetExistingManager()); + Assert.AreEqual(null, World.GetExistingManager()); + + Assert.IsFalse(system.Created); + } + + [Test] + public void OnCreateThrowRemovesSystem() + { + Assert.Throws(() => { World.CreateManager(); }); + Assert.AreEqual(null, World.GetExistingManager()); + } + + [Test] + public void DestroySystemWhileJobUsingArrayIsRunningWorks() + { + var system = World.CreateManager(); + system.Update(); + World.DestroyManager(system); + } + + [Test] + public void DisposeSystemComponentGroupThrows() + { + var system = World.CreateManager(); + var group = system.GetComponentGroup(typeof(EcsTestData)); + Assert.Throws(() => group.Dispose()); + } + + [Test] + public void DestroyManagerTwiceThrows() + { + var system = World.CreateManager(); + World.DestroyManager(system); + Assert.Throws(() => World.DestroyManager(system) ); + } + + [Test] + public void CreateTwoSystemsOfSameType() + { + var systemA = World.CreateManager(); + var systemB = World.CreateManager(); + // CreateManager makes a new manager + Assert.AreNotEqual(systemA, systemB); + // Return first system + Assert.AreEqual(systemA, World.GetOrCreateManager()); + } + + [Test] + public void CreateTwoSystemsAfterDestroyReturnSecond() + { + var systemA = World.CreateManager(); + var systemB = World.CreateManager(); + World.DestroyManager(systemA); + + Assert.AreEqual(systemB, World.GetExistingManager());; + } + + [Test] + public void CreateTwoSystemsAfterDestroyReturnFirst() + { + var systemA = World.CreateManager(); + var systemB = World.CreateManager(); + World.DestroyManager(systemB); + + Assert.AreEqual(systemA, World.GetExistingManager());; + } + + [Test] + public void GetComponentGroup() + { + ComponentType[] ro_rw = { ComponentType.ReadOnly(), typeof(EcsTestData2) }; + ComponentType[] rw_rw = { typeof(EcsTestData), typeof(EcsTestData2) }; + ComponentType[] rw = { typeof(EcsTestData) }; + + var ro_rw0_system = EmptySystem.GetComponentGroup(ro_rw); + var rw_rw_system = EmptySystem.GetComponentGroup(rw_rw); + var rw_system = EmptySystem.GetComponentGroup(rw); + + Assert.AreEqual(ro_rw0_system, EmptySystem.GetComponentGroup(ro_rw)); + Assert.AreEqual(rw_rw_system, EmptySystem.GetComponentGroup(rw_rw)); + Assert.AreEqual(rw_system, EmptySystem.GetComponentGroup(rw)); + + Assert.AreEqual(3, EmptySystem.ComponentGroups.Length); + } + + //@TODO: Behaviour is a slightly dodgy... Should probably just ignore and return same as single typeof(EcsTestData) + [Test] + public void GetComponentGroupWithEntityThrows() + { + ComponentType[] e = { typeof(Entity), typeof(EcsTestData) }; + EmptySystem.GetComponentGroup(e); + Assert.Throws(() => EmptySystem.GetComponentGroup(e)); + } + + //@TODO: Behaviour is a slightly dodgy... Optimally would always return the same ComponentGroup + [Test] + public void GetComponentGroupWithDuplicates() + { + // Currently duplicates will create two seperate groups doing the same thing... + ComponentType[] dup_1 = { typeof(EcsTestData2) }; + ComponentType[] dup_2 = { typeof(EcsTestData2), typeof(EcsTestData2) }; + + var dup1_system = EmptySystem.GetComponentGroup(dup_1); + var dup2_system = EmptySystem.GetComponentGroup(dup_2); + + Assert.AreEqual(dup1_system, EmptySystem.GetComponentGroup(dup_1)); + Assert.AreEqual(dup2_system, EmptySystem.GetComponentGroup(dup_2)); + + Assert.AreEqual(2, EmptySystem.ComponentGroups.Length); + } + + } +} \ No newline at end of file diff --git a/Unity.Entities.Tests/ComponentSystemTests.cs.meta b/Unity.Entities.Tests/ComponentSystemTests.cs.meta new file mode 100644 index 00000000..d98935ae --- /dev/null +++ b/Unity.Entities.Tests/ComponentSystemTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a799b64c869341b88980da7de22b90b3 +timeCreated: 1512128499 \ No newline at end of file diff --git a/Unity.Entities.Tests/CreateAndDestroyTests.cs b/Unity.Entities.Tests/CreateAndDestroyTests.cs new file mode 100644 index 00000000..fa28a84d --- /dev/null +++ b/Unity.Entities.Tests/CreateAndDestroyTests.cs @@ -0,0 +1,217 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class CreateAndDestroyTests : ECSTestsFixture + { + [Test] + unsafe public void CreateAndDestroyOne() + { + var entity = CreateEntityWithDefaultData(10); + m_Manager.DestroyEntity(entity); + AssertDoesNotExist(entity); + } + + [Test] + unsafe public void EmptyEntityIsNull() + { + CreateEntityWithDefaultData(10); + Assert.IsFalse(m_Manager.Exists(new Entity())); + } + + [Test] + unsafe public void CreateAndDestroyTwo() + { + var entity0 = CreateEntityWithDefaultData(10); + var entity1 = CreateEntityWithDefaultData(11); + + m_Manager.DestroyEntity(entity0); + + AssertDoesNotExist(entity0); + AssertComponentData(entity1, 11); + + m_Manager.DestroyEntity(entity1); + AssertDoesNotExist(entity0); + AssertDoesNotExist(entity1); + } + + [Test] + unsafe public void CreateZeroEntities() + { + var array = new NativeArray(0, Allocator.Temp); + m_Manager.CreateEntity(m_Manager.CreateArchetype(typeof(EcsTestData)), array); + array.Dispose(); + } + + [Test] + unsafe public void InstantiateZeroEntities() + { + var array = new NativeArray(0, Allocator.Temp); + + var srcEntity = m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.Instantiate(srcEntity , array); + array.Dispose(); + } + + + [Test] + unsafe public void CreateAndDestroyThree() + { + var entity0 = CreateEntityWithDefaultData(10); + var entity1 = CreateEntityWithDefaultData(11); + + m_Manager.DestroyEntity(entity0); + + var entity2 = CreateEntityWithDefaultData(12); + + + AssertDoesNotExist(entity0); + + AssertComponentData(entity1, 11); + AssertComponentData(entity2, 12); + } + + [Test] + unsafe public void CreateAndDestroyStressTest() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + var entities = new NativeArray(10000, Allocator.Persistent); + + m_Manager.CreateEntity(archetype, entities); + + for (int i = 0; i < entities.Length; i++) + AssertComponentData(entities[i], 0); + + m_Manager.DestroyEntity(entities); + entities.Dispose(); + } + + [Test] + unsafe public void CreateAndDestroyShuffleStressTest() + { + Entity[] entities = new Entity[10000]; + for (int i = 0; i < entities.Length;i++) + { + entities[i] = CreateEntityWithDefaultData(i); + } + + for (int i = 0; i < entities.Length; i++) + { + if (i % 2 == 0) + m_Manager.DestroyEntity(entities[i]); + } + + for (int i = 0; i < entities.Length; i++) + { + if (i % 2 == 0) + { + AssertDoesNotExist(entities[i]); + } + else + { + AssertComponentData(entities[i], i); + } + } + + for (int i = 0; i < entities.Length; i++) + { + if (i % 2 == 1) + m_Manager.DestroyEntity(entities[i]); + } + + for (int i = 0; i < entities.Length; i++) + { + AssertDoesNotExist(entities[i]); + } + } + + + [Test] + unsafe public void InstantiateStressTest() + { + var entities = new NativeArray(10000, Allocator.Persistent); + var srcEntity = CreateEntityWithDefaultData(5); + + m_Manager.Instantiate(srcEntity, entities); + + for (int i = 0; i < entities.Length; i++) + AssertComponentData(entities[i], 5); + + m_Manager.DestroyEntity(entities); + entities.Dispose(); + } + + [Test] + public void AddRemoveComponent() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var entity = m_Manager.CreateEntity(archetype); + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsFalse(m_Manager.HasComponent(entity)); + + m_Manager.AddComponentData(entity, new EcsTestData3(3)); + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsTrue(m_Manager.HasComponent(entity)); + + Assert.AreEqual(3, m_Manager.GetComponentData(entity).value0); + Assert.AreEqual(3, m_Manager.GetComponentData(entity).value1); + Assert.AreEqual(3, m_Manager.GetComponentData(entity).value2); + + m_Manager.RemoveComponent(entity); + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsFalse(m_Manager.HasComponent(entity)); + Assert.IsTrue(m_Manager.HasComponent(entity)); + + Assert.AreEqual(3, m_Manager.GetComponentData(entity).value0); + Assert.AreEqual(3, m_Manager.GetComponentData(entity).value1); + Assert.AreEqual(3, m_Manager.GetComponentData(entity).value2); + + m_Manager.DestroyEntity(entity); + } + + [Test] + public void AddComponentSetsValueOfComponentToDefault() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData)); + var dummyEntity = m_Manager.CreateEntity(archetype); + m_Manager.Debug.PoisonUnusedDataInAllChunks(archetype, 0xCD); + + var entity = m_Manager.CreateEntity(); + m_Manager.AddComponent(entity, ComponentType.Create()); + Assert.AreEqual(0, m_Manager.GetComponentData(entity).value); + + m_Manager.DestroyEntity(dummyEntity); + m_Manager.DestroyEntity(entity); + } + + [Test] + public void ReadOnlyAndNonReadOnlyArchetypeAreEqual() + { + var arch = m_Manager.CreateArchetype(ComponentType.ReadOnly(typeof(EcsTestData))); + var arch2 = m_Manager.CreateArchetype(typeof(EcsTestData)); + Assert.AreEqual(arch, arch2); + } + + [Test] + public void SubtractiveArchetypeReactToAddRemoveComponent() + { + var subtractiveArch = m_Manager.CreateComponentGroup(ComponentType.Subtractive(typeof(EcsTestData)), typeof(EcsTestData2)); + + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var entity = m_Manager.CreateEntity(archetype); + Assert.AreEqual(0, subtractiveArch.GetComponentDataArray().Length); + + m_Manager.RemoveComponent(entity); + Assert.AreEqual(1, subtractiveArch.GetComponentDataArray().Length); + + m_Manager.AddComponentData(entity, new EcsTestData()); + Assert.AreEqual(0, subtractiveArch.GetComponentDataArray().Length); + } + } +} diff --git a/Unity.Entities.Tests/CreateAndDestroyTests.cs.meta b/Unity.Entities.Tests/CreateAndDestroyTests.cs.meta new file mode 100644 index 00000000..873d16db --- /dev/null +++ b/Unity.Entities.Tests/CreateAndDestroyTests.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: ebd7644e2bdab4f99ae51951050d9aa1 +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/ECSTestsFixture.cs b/Unity.Entities.Tests/ECSTestsFixture.cs new file mode 100644 index 00000000..53e4d68f --- /dev/null +++ b/Unity.Entities.Tests/ECSTestsFixture.cs @@ -0,0 +1,107 @@ +using NUnit.Framework; +using Unity.Entities; +using Unity.Jobs; + +namespace Unity.Entities.Tests +{ + [DisableAutoCreation] + public class EmptySystem : JobComponentSystem + { + protected override JobHandle OnUpdate(JobHandle dep) { return dep; } + + + new public ComponentGroup GetComponentGroup(params ComponentType[] componentTypes) + { + return base.GetComponentGroup(componentTypes); + } + + new public ComponentGroupArray GetEntities() where T : struct + { + return base.GetEntities(); + } + } + + public class ECSTestsFixture + { + protected World m_PreviousWorld; + protected World World; + protected EntityManager m_Manager; + + protected int StressTestEntityCount = 1000; + + [SetUp] + public virtual void Setup() + { + m_PreviousWorld = World.Active; + World = World.Active = new World("Test World"); + + m_Manager = World.GetOrCreateManager(); + } + + [TearDown] + public virtual void TearDown() + { + if (m_Manager != null) + { + World.Dispose(); + World = null; + + World.Active = m_PreviousWorld; + m_PreviousWorld = null; + m_Manager = null; + } + } + + public void AssertDoesNotExist(Entity entity) + { + Assert.IsFalse(m_Manager.HasComponent(entity)); + Assert.IsFalse(m_Manager.HasComponent(entity)); + Assert.IsFalse(m_Manager.HasComponent(entity)); + Assert.IsFalse(m_Manager.Exists(entity)); + } + + public void AssertComponentData(Entity entity, int index) + { + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsFalse(m_Manager.HasComponent(entity)); + Assert.IsTrue(m_Manager.Exists(entity)); + + Assert.AreEqual(-index, m_Manager.GetComponentData(entity).value0); + Assert.AreEqual(-index, m_Manager.GetComponentData(entity).value1); + Assert.AreEqual(index, m_Manager.GetComponentData(entity).value); + } + + public Entity CreateEntityWithDefaultData(int index) + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + // HasComponent & Exists setup correctly + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsTrue(m_Manager.HasComponent(entity)); + Assert.IsFalse(m_Manager.HasComponent(entity)); + Assert.IsTrue(m_Manager.Exists(entity)); + + // Create must initialize values to zero + Assert.AreEqual(0, m_Manager.GetComponentData(entity).value0); + Assert.AreEqual(0, m_Manager.GetComponentData(entity).value1); + Assert.AreEqual(0, m_Manager.GetComponentData(entity).value); + + // Setup some non zero default values + m_Manager.SetComponentData(entity, new EcsTestData2(-index)); + m_Manager.SetComponentData(entity, new EcsTestData(index)); + + AssertComponentData(entity, index); + + return entity; + } + + public EmptySystem EmptySystem + { + get + { + return World.Active.GetOrCreateManager(); + } + } + } +} diff --git a/Unity.Entities.Tests/ECSTestsFixture.cs.meta b/Unity.Entities.Tests/ECSTestsFixture.cs.meta new file mode 100644 index 00000000..215ae633 --- /dev/null +++ b/Unity.Entities.Tests/ECSTestsFixture.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: f8a28098052354f2eb760dabe785c2a2 +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/EntityCommandBufferTests.cs b/Unity.Entities.Tests/EntityCommandBufferTests.cs new file mode 100644 index 00000000..6ed6cb43 --- /dev/null +++ b/Unity.Entities.Tests/EntityCommandBufferTests.cs @@ -0,0 +1,485 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using System.Collections.Generic; + +namespace Unity.Entities.Tests +{ + public class EntityCommandBufferTests : ECSTestsFixture + { + [Test] + public void EmptyOK() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + cmds.Playback(m_Manager); + cmds.Dispose(); + } + + struct TestJob : IJob + { + public EntityCommandBuffer Buffer; + + public void Execute() + { + Buffer.CreateEntity(); + Buffer.AddComponent(new EcsTestData { value = 1 }); + } + } + + [Test] + public void SingleWriterEnforced() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + var job = new TestJob {Buffer = cmds}; + + cmds.CreateEntity(); + cmds.AddComponent(new EcsTestData { value = 42 }); + + var handle = job.Schedule(); + + Assert.Throws(() => { cmds.CreateEntity(); }); + Assert.Throws(() => { job.Buffer.CreateEntity(); }); + + handle.Complete(); + + cmds.Playback(m_Manager); + cmds.Dispose(); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(2, arr.Length); + Assert.AreEqual(42, arr[0].value); + Assert.AreEqual(1, arr[1].value); + group.Dispose(); + } + + [Test] + public void DisposeWhileJobRunningThrows() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + var job = new TestJob {Buffer = cmds}; + + var handle = job.Schedule(); + + Assert.Throws(() => { cmds.Dispose(); }); + + handle.Complete(); + + cmds.Dispose(); + } + + [Test] + public void ModifiesWhileJobRunningThrows() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + var job = new TestJob {Buffer = cmds}; + + var handle = job.Schedule(); + + Assert.Throws(() => { cmds.CreateEntity(); }); + + handle.Complete(); + + cmds.Dispose(); + } + + [Test] + public void PlaybackWhileJobRunningThrows() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + var job = new TestJob {Buffer = cmds}; + + var handle = job.Schedule(); + + Assert.Throws(() => { cmds.Playback(m_Manager); }); + + handle.Complete(); + + cmds.Dispose(); + } + + [Test] + public void ImplicitCreateEntity() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + cmds.CreateEntity(); + cmds.AddComponent(new EcsTestData { value = 12 }); + cmds.Playback(m_Manager); + cmds.Dispose(); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(1, arr.Length); + Assert.AreEqual(12, arr[0].value); + group.Dispose(); + } + + [Test] + public void ImplicitCreateEntityWithArchetype() + { + var a = m_Manager.CreateArchetype(typeof(EcsTestData)); + + var cmds = new EntityCommandBuffer(Allocator.TempJob); + cmds.CreateEntity(a); + cmds.SetComponent(new EcsTestData { value = 12 }); + cmds.Playback(m_Manager); + cmds.Dispose(); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(1, arr.Length); + Assert.AreEqual(12, arr[0].value); + group.Dispose(); + } + + [Test] + public void ImplicitCreateEntityTwice() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + cmds.CreateEntity(); + cmds.AddComponent(new EcsTestData { value = 12 }); + cmds.CreateEntity(); + cmds.AddComponent(new EcsTestData { value = 13 }); + cmds.Playback(m_Manager); + cmds.Dispose(); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(2, arr.Length); + Assert.AreEqual(12, arr[0].value); + Assert.AreEqual(13, arr[1].value); + group.Dispose(); + } + + [Test] + public void ImplicitCreateTwoComponents() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + cmds.CreateEntity(); + cmds.AddComponent(new EcsTestData { value = 12 }); + cmds.AddComponent(new EcsTestData2 { value0 = 1, value1 = 2 }); + cmds.Playback(m_Manager); + cmds.Dispose(); + + { + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(1, arr.Length); + Assert.AreEqual(12, arr[0].value); + group.Dispose(); + } + + { + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData2)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(1, arr.Length); + Assert.AreEqual(1, arr[0].value0); + Assert.AreEqual(2, arr[0].value1); + group.Dispose(); + } + } + + [Test] + public void TestMultiChunks() + { + const int count = 65536; + + var cmds = new EntityCommandBuffer(Allocator.TempJob); + cmds.MinimumChunkSize = 512; + + for (int i = 0; i < count; ++i) + { + cmds.CreateEntity(); + cmds.AddComponent(new EcsTestData { value = i }); + cmds.AddComponent(new EcsTestData2 { value0 = i, value1 = i }); + } + + cmds.Playback(m_Manager); + cmds.Dispose(); + + { + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(EcsTestData2)); + var arr = group.GetComponentDataArray(); + var arr2 = group.GetComponentDataArray(); + Assert.AreEqual(count, arr.Length); + for (int i = 0; i < count; ++i) + { + Assert.AreEqual(i, arr[i].value); + Assert.AreEqual(i, arr2[i].value0); + Assert.AreEqual(i, arr2[i].value1); + } + group.Dispose(); + } + } + + [Test] + public void AddSharedComponent() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + var entity = m_Manager.CreateEntity(); + cmds.AddSharedComponent(entity, new EcsTestSharedComp(10)); + cmds.AddSharedComponent(entity, new EcsTestSharedComp2(20)); + + cmds.Playback(m_Manager); + + Assert.AreEqual(10, m_Manager.GetSharedComponentData(entity).value); + Assert.AreEqual(20, m_Manager.GetSharedComponentData(entity).value1); + + cmds.Dispose(); + } + + [Test] + public void SetSharedComponent() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + var entity = m_Manager.CreateEntity(); + var sharedComponent = new EcsTestSharedComp(10); + m_Manager.AddSharedComponentData(entity, sharedComponent); + + cmds.SetSharedComponent(entity, new EcsTestSharedComp(33)); + + cmds.Playback(m_Manager); + + Assert.AreEqual(33, m_Manager.GetSharedComponentData(entity).value); + + cmds.Dispose(); + } + + [Test] + public void RemoveSharedComponent() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + var entity = m_Manager.CreateEntity(); + var sharedComponent = new EcsTestSharedComp(10); + m_Manager.AddSharedComponentData(entity, sharedComponent); + + cmds.RemoveComponent(entity); + + cmds.Playback(m_Manager); + + Assert.IsFalse(m_Manager.HasComponent(entity), "The shared component was not removed."); + + cmds.Dispose(); + } + + [Test] + public void ImplicitAddSharedComponent() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + cmds.CreateEntity(); + cmds.AddSharedComponent(new EcsTestSharedComp(10)); + cmds.AddSharedComponent(new EcsTestSharedComp2(20)); + + cmds.Playback(m_Manager); + + var sharedComp1List = new List(); + var sharedComp2List = new List(); + + m_Manager.GetAllUniqueSharedComponentDatas(sharedComp1List); + m_Manager.GetAllUniqueSharedComponentDatas(sharedComp2List); + + // the count must be 2 - the default value of the shared component and the one we actually set + Assert.AreEqual(2, sharedComp1List.Count); + Assert.AreEqual(2, sharedComp2List.Count); + + Assert.AreEqual(10, sharedComp1List[1].value); + Assert.AreEqual(20, sharedComp2List[1].value1); + + cmds.Dispose(); + } + + [Test] + public void ImplicitSetSharedComponent() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + cmds.CreateEntity(); + cmds.AddSharedComponent(new EcsTestSharedComp(10)); + cmds.SetSharedComponent(new EcsTestSharedComp(33)); + + cmds.Playback(m_Manager); + + var sharedCompList = new List(); + m_Manager.GetAllUniqueSharedComponentDatas(sharedCompList); + + Assert.AreEqual(2, sharedCompList.Count); + Assert.AreEqual(33, sharedCompList[1].value); + + cmds.Dispose(); + } + + [Test] + public void ImplicitSetSharedComponentDefault() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + cmds.CreateEntity(); + cmds.AddSharedComponent(new EcsTestSharedComp()); + cmds.SetSharedComponent(new EcsTestSharedComp()); + + cmds.Playback(m_Manager); + + var sharedCompList = new List(); + m_Manager.GetAllUniqueSharedComponentDatas(sharedCompList); + + Assert.AreEqual(1, sharedCompList.Count); + Assert.AreEqual(0, sharedCompList[0].value); + + cmds.Dispose(); + } + + struct TestJobWithManagedSharedData : IJob + { + public EntityCommandBuffer Buffer; + public EcsTestSharedComp2 Blah; + + public void Execute() + { + Buffer.CreateEntity(); + Buffer.AddSharedComponent(Blah); + } + } + + [Test] + public void JobWithSharedComponentData() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + var job = new TestJobWithManagedSharedData { Buffer = cmds, Blah = new EcsTestSharedComp2(12) }; + + job.Schedule().Complete(); + cmds.Playback(m_Manager); + cmds.Dispose(); + + var list = new List(); + m_Manager.GetAllUniqueSharedComponentDatas(list); + + Assert.AreEqual(2, list.Count); + Assert.AreEqual(0, list[0].value0); + Assert.AreEqual(0, list[0].value1); + Assert.AreEqual(12, list[1].value0); + Assert.AreEqual(12, list[1].value1); + } + + // TODO: Burst breaks this test. + //[ComputeJobOptimization(CompileSynchronously = true)] + public struct TestBurstCommandBufferJob : IJob + { + public Entity e0; + public Entity e1; + public EntityCommandBuffer Buffer; + + public void Execute() + { + Buffer.DestroyEntity(e0); + Buffer.DestroyEntity(e1); + } + } + + [Test] + public void TestCommandBufferDelete() + { + Entity[] entities = new Entity[2]; + for (int i = 0; i < entities.Length; ++i) + { + entities[i] = m_Manager.CreateEntity(); + m_Manager.AddComponentData(entities[i], new EcsTestData { value = i }); + } + + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + new TestBurstCommandBufferJob { + e0 = entities[0], + e1 = entities[1], + Buffer = cmds, + }.Schedule().Complete(); + + cmds.Playback(m_Manager); + + cmds.Dispose(); + + var allEntities = m_Manager.GetAllEntities(); + int count = allEntities.Length; + allEntities.Dispose(); + + Assert.AreEqual(0, count); + } + + [Test] + public void TestCommandBufferDeleteWithSystemState() + { + Entity[] entities = new Entity[2]; + for (int i = 0; i < entities.Length; ++i) + { + entities[i] = m_Manager.CreateEntity(); + m_Manager.AddComponentData(entities[i], new EcsTestData { value = i }); + m_Manager.AddComponentData(entities[i], new EcsState1 { Value = i }); + } + + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + new TestBurstCommandBufferJob { + e0 = entities[0], + e1 = entities[1], + Buffer = cmds, + }.Schedule().Complete(); + + cmds.Playback(m_Manager); + + cmds.Dispose(); + + var allEntities = m_Manager.GetAllEntities(); + int count = allEntities.Length; + allEntities.Dispose(); + + Assert.AreEqual(entities.Length, count); + } + + [Test] + public void TestCommandBufferDeleteRemoveSystemState() + { + Entity[] entities = new Entity[2]; + for (int i = 0; i < entities.Length; ++i) + { + entities[i] = m_Manager.CreateEntity(); + m_Manager.AddComponentData(entities[i], new EcsTestData { value = i }); + m_Manager.AddComponentData(entities[i], new EcsState1 { Value = i }); + } + + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + new TestBurstCommandBufferJob + { + e0 = entities[0], + e1 = entities[1], + Buffer = cmds, + }.Schedule().Complete(); + + cmds.Playback(m_Manager); + cmds.Dispose(); + } + + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + for (var i = 0; i < entities.Length; i++) + { + cmds.RemoveSystemStateComponent(entities[i]); + } + + cmds.Playback(m_Manager); + cmds.Dispose(); + } + + var allEntities = m_Manager.GetAllEntities(); + int count = allEntities.Length; + allEntities.Dispose(); + + Assert.AreEqual(0, count); + } + } +} diff --git a/Unity.Entities.Tests/EntityCommandBufferTests.cs.meta b/Unity.Entities.Tests/EntityCommandBufferTests.cs.meta new file mode 100644 index 00000000..ecd56ef5 --- /dev/null +++ b/Unity.Entities.Tests/EntityCommandBufferTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 995ad52a276f96847ac9d0e653099794 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/EntityManagerTests.cs b/Unity.Entities.Tests/EntityManagerTests.cs new file mode 100644 index 00000000..5772b7e5 --- /dev/null +++ b/Unity.Entities.Tests/EntityManagerTests.cs @@ -0,0 +1,86 @@ +using Unity.Collections; +using NUnit.Framework; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + interface IEcsFooInterface + { + int value { get; set; } + + } + public struct EcsFooTest : IComponentData, IEcsFooInterface + { + public int value { get; set; } + + public EcsFooTest(int inValue) { value = inValue; } + } + + interface IEcsBarInterface + { + int value { get; set; } + + } + public struct EcsBarTest : IComponentData, IEcsBarInterface + { + public int value { get; set; } + + public EcsBarTest(int inValue) { value = inValue; } + } + + public class EntityManagerTests : ECSTestsFixture + { + [Test] + public void IncreaseEntityCapacity() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData)); + var count = 1024; + var array = new NativeArray(count, Allocator.Temp); + m_Manager.CreateEntity (archetype, array); + for (int i = 0; i < count; i++) + { + Assert.AreEqual(i, array[i].Index); + } + array.Dispose(); + } + + [Test] + public void FoundComponentInterface() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData),typeof(EcsFooTest)); + var count = 1024; + var array = new NativeArray(count, Allocator.Temp); + m_Manager.CreateEntity (archetype, array); + + var fooTypes = m_Manager.GetAssignableComponentTypes(typeof(IEcsFooInterface)); + Assert.AreEqual(1,fooTypes.Count); + Assert.AreEqual(typeof(EcsFooTest),fooTypes[0]); + + var barTypes = m_Manager.GetAssignableComponentTypes(typeof(IEcsBarInterface)); + Assert.AreEqual(0,barTypes.Count); + + array.Dispose(); + } + + [Test] + public void VersionIsConsistent() + { + Assert.AreEqual(0, m_Manager.Version); + + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + Assert.AreEqual(1, m_Manager.Version); + + m_Manager.AddComponentData(entity, new EcsTestData2(0)); + Assert.AreEqual(2, m_Manager.Version); + + m_Manager.SetComponentData(entity, new EcsTestData2(5)); + Assert.AreEqual(2, m_Manager.Version); // Shouldn't change when just setting data + + m_Manager.RemoveComponent(entity); + Assert.AreEqual(3, m_Manager.Version); + + m_Manager.DestroyEntity(entity); + Assert.AreEqual(4, m_Manager.Version); + } + } +} diff --git a/Unity.Entities.Tests/EntityManagerTests.cs.meta b/Unity.Entities.Tests/EntityManagerTests.cs.meta new file mode 100644 index 00000000..53cfbdc1 --- /dev/null +++ b/Unity.Entities.Tests/EntityManagerTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 975c546cf0707a64ea481e45e76a6153 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/EntityTransactionTests.cs b/Unity.Entities.Tests/EntityTransactionTests.cs new file mode 100644 index 00000000..8bb3aead --- /dev/null +++ b/Unity.Entities.Tests/EntityTransactionTests.cs @@ -0,0 +1,162 @@ +using System; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Unity.Jobs; +using Unity.Collections; +using Unity.Entities; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Entities.Tests +{ + public class EntityTransactionTests : ECSTestsFixture + { + ComponentGroup m_Group; + + public EntityTransactionTests() + { + Assert.IsTrue(Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobDebuggerEnabled, "JobDebugger must be enabled for these tests"); + } + + [SetUp] + public override void Setup() + { + base.Setup(); + + m_Group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + + // Archetypes can't be created on a job + m_Manager.CreateArchetype(typeof(EcsTestData)); + } + + struct CreateEntityAddToListJob : IJob + { + public ExclusiveEntityTransaction entities; + public NativeList createdEntities; + + public void Execute() + { + var entity = entities.CreateEntity(ComponentType.Create()); + entities.SetComponentData(entity, new EcsTestData(42)); + Assert.AreEqual(42, entities.GetComponentData(entity).value); + + createdEntities.Add(entity); + } + } + + struct CreateEntityJob : IJob + { + public ExclusiveEntityTransaction entities; + + public void Execute() + { + var entity = entities.CreateEntity(ComponentType.Create()); + entities.SetComponentData(entity, new EcsTestData(42)); + Assert.AreEqual(42, entities.GetComponentData(entity).value); + } + } + + [Test] + public void CreateEntitiesChainedJob() + { + var job = new CreateEntityAddToListJob(); + job.entities = m_Manager.BeginExclusiveEntityTransaction(); + job.createdEntities = new NativeList(0, Allocator.TempJob); + + m_Manager.ExclusiveEntityTransactionDependency = job.Schedule(m_Manager.ExclusiveEntityTransactionDependency); + m_Manager.ExclusiveEntityTransactionDependency = job.Schedule(m_Manager.ExclusiveEntityTransactionDependency); + + m_Manager.EndExclusiveEntityTransaction(); + + Assert.AreEqual(2, m_Group.CalculateLength()); + Assert.AreEqual(42, m_Group.GetComponentDataArray()[0].value); + Assert.AreEqual(42, m_Group.GetComponentDataArray()[1].value); + + Assert.IsTrue(m_Manager.Exists(job.createdEntities[0])); + Assert.IsTrue(m_Manager.Exists(job.createdEntities[1])); + + job.createdEntities.Dispose(); + } + + + [Test] + public void CommitAfterNotRegisteredTransactionJobLogsError() + { + var job = new CreateEntityJob(); + job.entities = m_Manager.BeginExclusiveEntityTransaction(); + + /*var jobHandle =*/ job.Schedule(m_Manager.ExclusiveEntityTransactionDependency); + + // Commit transaction expects an error not exception otherwise errors might occurr after a system has completed... + LogAssert.Expect(LogType.Error, new Regex("ExclusiveEntityTransaction job has not been registered")); + m_Manager.EndExclusiveEntityTransaction(); + } + + [Test] + public void EntityManagerAccessDuringTransactionThrows() + { + var job = new CreateEntityAddToListJob(); + job.entities = m_Manager.BeginExclusiveEntityTransaction(); + + Assert.Throws(() => { m_Manager.CreateEntity(typeof(EcsTestData)); }); + + //@TODO: + //Assert.Throws(() => { m_Manager.Exists(new Entity()); }); + } + + [Test] + public void AccessTransactionAfterEndTransactionThrows() + { + var transaction = m_Manager.BeginExclusiveEntityTransaction(); + m_Manager.EndExclusiveEntityTransaction(); + + Assert.Throws(() => { transaction.CreateEntity(typeof(EcsTestData)); }); + } + + [Test] + public void AccessExistingEntityFromTransactionWorks() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + + var transaction = m_Manager.BeginExclusiveEntityTransaction(); + Assert.AreEqual(42, transaction.GetComponentData(entity).value); + } + + [Test] + public void MissingJobCreationDependency() + { + var job = new CreateEntityJob(); + job.entities = m_Manager.BeginExclusiveEntityTransaction(); + + var jobHandle = job.Schedule(); + Assert.Throws(() => { job.Schedule(); }); + + jobHandle.Complete(); + } + + [Test] + public void CreationJobAndMainThreadNotAllowedInParallel() + { + var job = new CreateEntityJob(); + job.entities = m_Manager.BeginExclusiveEntityTransaction(); + + var jobHandle = job.Schedule(); + + Assert.Throws(() => { job.entities.CreateEntity(typeof(EcsTestData)); }); + + jobHandle.Complete(); + } + + [Test] + public void CreatingEntitiesBeyondCapacityInTransactionWorks() + { + var arch = m_Manager.CreateArchetype(typeof(EcsTestData)); + + var transaction = m_Manager.BeginExclusiveEntityTransaction(); + var entities = new NativeArray(1000, Allocator.Persistent); + transaction.CreateEntity(arch, entities); + entities.Dispose(); + } + } +} diff --git a/Unity.Entities.Tests/EntityTransactionTests.cs.meta b/Unity.Entities.Tests/EntityTransactionTests.cs.meta new file mode 100644 index 00000000..b83a7ec4 --- /dev/null +++ b/Unity.Entities.Tests/EntityTransactionTests.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: a25180f563def418f8a4494611d259ab +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/FastEqualityTests.cs b/Unity.Entities.Tests/FastEqualityTests.cs new file mode 100644 index 00000000..6f619b60 --- /dev/null +++ b/Unity.Entities.Tests/FastEqualityTests.cs @@ -0,0 +1,165 @@ +using System.Runtime.InteropServices; +using NUnit.Framework; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities.Tests +{ + public class FastEqualityTests + { + [StructLayout(LayoutKind.Sequential)] + struct Simple + { + int a; + int b; + } + + [StructLayout(LayoutKind.Sequential)] + struct SimpleEmbedded + { + float4 a; + int b; + } + + [StructLayout(LayoutKind.Sequential)] + + struct BytePadding + { + byte a; + byte b; + float c; + } + + [StructLayout(LayoutKind.Sequential)] + struct AlignSplit + { + float3 a; + double b; + } + + [StructLayout(LayoutKind.Sequential)] + struct EndPadding + { + double a; + float b; + + public EndPadding(double a, float b) + { + this.a = a; + this.b = b; + } + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct FloatPointer + { + float* a; + } + + [StructLayout(LayoutKind.Sequential)] + struct ClassInStruct + { + string blah; + } + + + int PtrAligned4Count = UnsafeUtility.SizeOf() / 4; + + [Test] + public void SimpleLayout() + { + var res = FastEquality.CreateLayout(typeof(Simple)); + Assert.AreEqual(1, res.Length); + Assert.AreEqual(new FastEquality.Layout {offset = 0, count = 2, Aligned4 = true }, res[0]); + + + } + + [Test] + public void PtrLayout() + { + var layout = FastEquality.CreateLayout(typeof(FloatPointer)); + Assert.AreEqual(1, layout.Length); + Assert.AreEqual(new FastEquality.Layout {offset = 0, count = PtrAligned4Count, Aligned4 = true }, layout[0]); + } + + [Test] + public void ClassLayout() + { + var layout = FastEquality.CreateLayout(typeof(ClassInStruct)); + Assert.AreEqual(1, layout.Length); + Assert.AreEqual(new FastEquality.Layout {offset = 0, count = PtrAligned4Count, Aligned4 = true }, layout[0]); + } + + [Test] + public void SimpleEmbeddedLayout() + { + var layout = FastEquality.CreateLayout(typeof(SimpleEmbedded)); + Assert.AreEqual(1, layout.Length); + Assert.AreEqual(new FastEquality.Layout {offset = 0, count = 5, Aligned4 = true }, layout[0]); + } + + [Test] + public void EndPaddingLayout() + { + var layout = FastEquality.CreateLayout(typeof(EndPadding)); + Assert.AreEqual(1, layout.Length); + Assert.AreEqual(new FastEquality.Layout {offset = 0, count = 3, Aligned4 = true }, layout[0]); + } + + [Test] + public void AlignSplitLayout() + { + var layout = FastEquality.CreateLayout(typeof(AlignSplit)); + Assert.AreEqual(2, layout.Length); + + Assert.AreEqual(new FastEquality.Layout {offset = 0, count = 3, Aligned4 = true }, layout[0]); + Assert.AreEqual(new FastEquality.Layout {offset = 16, count = 2, Aligned4 = true }, layout[1]); + } + + [Test] + public void BytePaddding() + { + var layout = FastEquality.CreateLayout(typeof(BytePadding)); + Assert.AreEqual(2, layout.Length); + + Assert.AreEqual(new FastEquality.Layout {offset = 0, count = 2, Aligned4 = false }, layout[0]); + Assert.AreEqual(new FastEquality.Layout {offset = 4, count = 1, Aligned4 = true }, layout[1]); + } + + [Test] + public void EqualsInt4() + { + var layout = FastEquality.CreateLayout(typeof(int4)); + + Assert.IsTrue(FastEquality.Equals(new int4(1, 2, 3, 4), new int4(1, 2, 3, 4), layout)); + Assert.IsFalse(FastEquality.Equals(new int4(1, 2, 3, 4), new int4(1, 2, 3, 5), layout)); + Assert.IsFalse(FastEquality.Equals(new int4(1, 2, 3, 4), new int4(0, 2, 3, 4), layout)); + Assert.IsFalse(FastEquality.Equals(new int4(1, 2, 3, 4), new int4(5, 6, 7, 8), layout)); + } + + [Test] + public void EqualsPadding() + { + var layout = FastEquality.CreateLayout(typeof(EndPadding)); + + Assert.IsTrue(FastEquality.Equals(new EndPadding(1, 2), new EndPadding(1, 2), layout)); + Assert.IsFalse(FastEquality.Equals(new EndPadding(1, 2), new EndPadding(1, 3), layout)); + Assert.IsFalse(FastEquality.Equals(new EndPadding(1, 2), new EndPadding(4, 2), layout)); + } + + [Test] + unsafe public void GetHashCodeInt4() + { + var layout = FastEquality.CreateLayout(typeof(int4)); + Assert.AreEqual(-270419516, FastEquality.GetHashCode(new int4(1, 2, 3, 4), layout)); + Assert.AreEqual(-270419517, FastEquality.GetHashCode(new int4(1, 2, 3, 3), layout)); + Assert.AreEqual(1, FastEquality.GetHashCode(new int4(0, 0, 0, 1), layout)); + Assert.AreEqual(16777619, FastEquality.GetHashCode(new int4(0, 0, 1, 0), layout)); + Assert.AreEqual(0, FastEquality.GetHashCode(new int4(0, 0, 0, 0), layout)); + + // Note, builtin .GetHashCode() returns different values even for all zeros... + } + } +} diff --git a/Unity.Entities.Tests/FastEqualityTests.cs.meta b/Unity.Entities.Tests/FastEqualityTests.cs.meta new file mode 100644 index 00000000..3812810e --- /dev/null +++ b/Unity.Entities.Tests/FastEqualityTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 504677b42b9264279a9aa4944dbae017 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/FixedArrayTests.cs b/Unity.Entities.Tests/FixedArrayTests.cs new file mode 100644 index 00000000..d1e28a9c --- /dev/null +++ b/Unity.Entities.Tests/FixedArrayTests.cs @@ -0,0 +1,181 @@ +using NUnit.Framework; +using Unity.Collections; +using System; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class FixedArrayTests : ECSTestsFixture + { + [Test] + public void CreateEntityWithTwoSameTypeFixedArraysThrows() + { + var array11Type = ComponentType.FixedArray(typeof(int), 11); + var array12Type = ComponentType.FixedArray(typeof(int), 12); + Assert.Throws(() => { m_Manager.CreateEntity(array11Type, array12Type); }); + } + + [Test] + public void GetComponentFixedArrayAgainstIComponentDataThrows() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + Assert.Throws(() => { m_Manager.GetFixedArray(entity); }); + } + + [Test] + public void CreatingFixedArrayOfIComponentDataThrows() + { + Assert.Throws(() => { m_Manager.CreateEntity(ComponentType.FixedArray(typeof(EcsTestData), 2)); }); + } + + [Test] + public void CreateEntityWithIntThrows() + { + Assert.Throws(() => { m_Manager.CreateEntity(typeof(int));}); + } + + [Test] + public void AddComponentWithIntThrows() + { + var entity = m_Manager.CreateEntity(); + Assert.Throws(() => { m_Manager.AddComponent(entity, ComponentType.Create()); }); + } + + [Test] + public void CreateEntityArrayWithValidLengths([Values(0, 1, 2, 100)]int length) + { + var entity = m_Manager.CreateEntity(ComponentType.FixedArray(typeof(int), length)); + + var array = m_Manager.GetFixedArray(entity); + Assert.AreEqual(length, array.Length); + } + + [Test] + [TestCase(-1)] + // Invalid because chunk size is too small to hold a single entity + [TestCase(1024 * 1024)] + [Ignore("Crashes")] + public void CreateEntityWithInvalidFixedArraySize(int length) + { + var arrayType = ComponentType.FixedArray(typeof(int), length); + Assert.Throws(() => m_Manager.CreateEntity(arrayType)); + } + + [Test] + public void HasComponent() + { + var array11Type = ComponentType.FixedArray(typeof(int), 11); + var array12Type = ComponentType.FixedArray(typeof(int), 12); + var entity = m_Manager.CreateEntity(array11Type); + + Assert.IsTrue(m_Manager.HasComponent(entity, typeof(int))); + Assert.IsTrue(m_Manager.HasComponent(entity, array11Type)); + + Assert.IsFalse(m_Manager.HasComponent(entity, array12Type)); + } + + [Test] + public void RemoveComponentWithUnspecifiedLength() + { + var entity = m_Manager.CreateEntity(ComponentType.FixedArray(typeof(int), 11)); + m_Manager.RemoveComponent(entity, typeof(int)); + Assert.IsFalse(m_Manager.HasComponent(entity, typeof(int))); + } + + [Test] + public void RemoveComponentWithExactLength() + { + var fixed11 = ComponentType.FixedArray(typeof(int), 11); + var entity = m_Manager.CreateEntity(fixed11 ); + m_Manager.RemoveComponent(entity, fixed11 ); + Assert.IsFalse(m_Manager.HasComponent(entity, typeof(int))); + } + + [Test] + public void RemoveComponentWithIncorrectLength() + { + var fixed11 = ComponentType.FixedArray(typeof(int), 11); + var fixed1 = ComponentType.FixedArray(typeof(int), 1); + var entity = m_Manager.CreateEntity(fixed11 ); + Assert.Throws(() => { m_Manager.RemoveComponent(entity, fixed1); }); + } + + [Test] + public void MutateFixedArrayData() + { + var entity = m_Manager.CreateEntity(); + m_Manager.AddComponent(entity, ComponentType.FixedArray(typeof(int), 11)); + + var array = m_Manager.GetFixedArray(entity); + + Assert.AreEqual(11, array.Length); + array[7] = 5; + Assert.AreEqual(5, array[7]); + } + + //@TODO: Should really test additional constraint against exact size as well... + + [Test] + public void FixedArrayComponentGroupIteration() + { + /*var entity64 =*/ m_Manager.CreateEntity(ComponentType.FixedArray(typeof(int), 64)); + /*var entity10 =*/ m_Manager.CreateEntity(ComponentType.FixedArray(typeof(int), 10)); + + var group = m_Manager.CreateComponentGroup(typeof(int)); + + var fixedArray = group.GetFixedArrayArray(); + + Assert.AreEqual(2, fixedArray.Length); + Assert.AreEqual(64, fixedArray[0].Length); + Assert.AreEqual(10, fixedArray[1].Length); + + Assert.AreEqual(0, fixedArray[0][3]); + Assert.AreEqual(0, fixedArray[1][3]); + + NativeArray array; + + array = fixedArray[0]; + array[3] = 0; + + array = fixedArray[1]; + array[3] = 1; + + for (int i = 0; i < fixedArray.Length; i++) + { + Assert.AreEqual(i, fixedArray[i][3]); + } + } + + [Test] + public void FixedArrayFromEntityWorks() + { + var entityInt = m_Manager.CreateEntity(ComponentType.FixedArray(typeof(int), 3)); + m_Manager.GetFixedArray(entityInt).CopyFrom(new int[] { 1, 2, 3}); + + var intLookup = EmptySystem.GetFixedArrayFromEntity(); + Assert.IsTrue(intLookup.Exists(entityInt)); + Assert.IsFalse(intLookup.Exists(new Entity())); + + Assert.AreEqual(2, intLookup[entityInt][1]); + } + + + [Test] + [Ignore("Should work, need to write test")] + public void FixedArrayReadingFromTwoJobsInParallel() + { + } + + [Test] + [Ignore("Should work, need to write test")] + public void FixedArrayWritingInJob() + { + } + + [Test] + [Ignore("Should work, need to write test")] + public void FixedArrayCantBeWrittenFromTwoJobsInParallel() + { + } + } +} diff --git a/Unity.Entities.Tests/FixedArrayTests.cs.meta b/Unity.Entities.Tests/FixedArrayTests.cs.meta new file mode 100644 index 00000000..035cd138 --- /dev/null +++ b/Unity.Entities.Tests/FixedArrayTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9cb7bc66e20047e38a79228db0503b20 +timeCreated: 1510846495 \ No newline at end of file diff --git a/Unity.Entities.Tests/IJobProcessComponentDataTests.cs b/Unity.Entities.Tests/IJobProcessComponentDataTests.cs new file mode 100644 index 00000000..2a2df463 --- /dev/null +++ b/Unity.Entities.Tests/IJobProcessComponentDataTests.cs @@ -0,0 +1,200 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class IJobProcessComponentDataTests :ECSTestsFixture + { + struct Process1 : IJobProcessComponentData + { + public void Execute(ref EcsTestData value) + { + value.value++; + } + } + + struct Process2 : IJobProcessComponentData + { + public void Execute([ReadOnly]ref EcsTestData src, ref EcsTestData2 dst) + { + dst.value1 = src.value; + } + } + + struct Process3 : IJobProcessComponentData + { + public void Execute([ReadOnly]ref EcsTestData src, ref EcsTestData2 dst1, ref EcsTestData3 dst2) + { + dst1.value1 = dst2.value2 = src.value; + } + } + + [Test] + public void JobProcessSimple() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + + new Process2().Run(EmptySystem); + + Assert.AreEqual(42, m_Manager.GetComponentData(entity).value1); + } + + [Test] + public void JobProcessComponentGroupCorrect() + { + ComponentType[] expectedTypes = { ComponentType.ReadOnly(), ComponentType.Create() }; + + new Process2().Run(EmptySystem); + var group = EmptySystem.GetComponentGroup(expectedTypes); + + Assert.AreEqual(1, EmptySystem.ComponentGroups.Length); + Assert.IsTrue(EmptySystem.ComponentGroups[0].CompareComponents(expectedTypes)); + Assert.AreEqual(group, EmptySystem.ComponentGroups[0]); + } + + public enum ProcessMode + { + Single, + Parallel, + Run + } + + void Schedule(ProcessMode mode) where T : struct, IBaseJobProcessComponentData + { + if (mode == ProcessMode.Parallel) + new T().Schedule(EmptySystem, 13).Complete(); + else if (mode == ProcessMode.Run) + new T().Run(EmptySystem); + else + new T().Schedule(EmptySystem).Complete(); + } + + [Theory] + public void JobProcessStress_1(ProcessMode mode) + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData)); + + var entities = new NativeArray(StressTestEntityCount, Allocator.Temp); + m_Manager.CreateEntity(archetype, entities); + + Schedule(mode); + + for (int i = 0; i < entities.Length; i++) + Assert.AreEqual(1, m_Manager.GetComponentData(entities[i]).value); + + entities.Dispose(); + } + + [Theory] + public void JobProcessStress_2(ProcessMode mode) + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var entities = new NativeArray(StressTestEntityCount, Allocator.Temp); + m_Manager.CreateEntity(archetype, entities); + + for (int i = 0;i(mode); + + for (int i = 0; i < entities.Length; i++) + Assert.AreEqual(i, m_Manager.GetComponentData(entities[i]).value1); + + entities.Dispose(); + } + + [Theory] + public void JobProcessStress_3(ProcessMode mode) + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2), typeof(EcsTestData3)); + + var entities = new NativeArray(StressTestEntityCount, Allocator.Temp); + m_Manager.CreateEntity(archetype, entities); + for (int i = 0;i(mode); + + for (int i = 0; i < entities.Length; i++) + { + Assert.AreEqual(0, m_Manager.GetComponentData(entities[i]).value0); + Assert.AreEqual(0, m_Manager.GetComponentData(entities[i]).value0); + Assert.AreEqual(0, m_Manager.GetComponentData(entities[i]).value1); + + Assert.AreEqual(i, m_Manager.GetComponentData(entities[i]).value1); + Assert.AreEqual(i, m_Manager.GetComponentData(entities[i]).value2); + } + + entities.Dispose(); + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + [Test] + public void JobWithMissingDependency() + { + Assert.IsTrue(Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobDebuggerEnabled, "JobDebugger must be enabled for these tests"); + + m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var job = new Process2().Schedule(EmptySystem, 64); + Assert.Throws(() => { new Process2().Schedule(EmptySystem, 64); }); + + job.Complete(); + } +#endif + + [RequireSubtractiveComponent(typeof(EcsTestData3))] + [RequireComponentTag(typeof(EcsTestData4))] + struct ProcessTagged : IJobProcessComponentData + { + public void Execute(ref EcsTestData src, ref EcsTestData2 dst) + { + dst.value1 = dst.value0 = src.value; + } + } + + void Test(bool didProcess, Entity entity) + { + m_Manager.SetComponentData(entity, new EcsTestData(42)); + + new ProcessTagged().Schedule(EmptySystem, 64).Complete(); + + if (didProcess) + Assert.AreEqual(42, m_Manager.GetComponentData(entity).value0); + else + Assert.AreEqual(0, m_Manager.GetComponentData(entity).value0); + } + + [Test] + public void JobProcessAdditionalRequirements() + { + var entityIgnore0 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2), typeof(EcsTestData3)); + Test(false, entityIgnore0); + + var entityIgnore1 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + Test(false, entityIgnore1); + + var entityProcess = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2), typeof(EcsTestData4)); + Test(true, entityProcess); + } + + + + + [Test] + [Ignore("TODO")] + public void TestCoverageFor_ComponentSystemBase_InjectNestedIJobProcessComponentDataJobs() + { + } + + [Test] + [Ignore("TODO")] + public void DuplicateComponentTypeParametersThrows() + { + } + } +} \ No newline at end of file diff --git a/Unity.Entities.Tests/IJobProcessComponentDataTests.cs.meta b/Unity.Entities.Tests/IJobProcessComponentDataTests.cs.meta new file mode 100644 index 00000000..b3122696 --- /dev/null +++ b/Unity.Entities.Tests/IJobProcessComponentDataTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b39183ad9c30d47418186bf4418a7421 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/InjectComponentGroupTests.cs b/Unity.Entities.Tests/InjectComponentGroupTests.cs new file mode 100644 index 00000000..7f206784 --- /dev/null +++ b/Unity.Entities.Tests/InjectComponentGroupTests.cs @@ -0,0 +1,257 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using UnityEngine; + +namespace Unity.Entities.Tests +{ + public class InjectComponentGroupTests : ECSTestsFixture + { + [DisableAutoCreation] + [AlwaysUpdateSystem] + public class PureEcsTestSystem : ComponentSystem + { + public struct DataAndEntites + { + public ComponentDataArray Data; + public EntityArray Entities; + public int Length; + } + + [Inject] + public DataAndEntites Group; + + protected override void OnUpdate() + { + } + } + + [DisableAutoCreation] + [AlwaysUpdateSystem] + public class PureReadOnlySystem : ComponentSystem + { + public struct Datas + { + [ReadOnly] + public ComponentDataArray Data; + } + + [Inject] + public Datas Group; + + protected override void OnUpdate() + { + } + } + + + + public struct SharedData : ISharedComponentData + { + public int value; + + public SharedData(int val) { value = val; } + } + + [DisableAutoCreation] + [AlwaysUpdateSystem] + public class SharedComponentSystem : ComponentSystem + { + public struct Datas + { + public ComponentDataArray Data; + [ReadOnly] public SharedComponentDataArray SharedData; + } + + [Inject] + public Datas Group; + + protected override void OnUpdate() + { + } + } + + [Test] + public void ReadOnlyComponentDataArray() + { + var readOnlySystem = World.GetOrCreateManager (); + + var go = m_Manager.CreateEntity (new ComponentType[0]); + m_Manager.AddComponentData (go, new EcsTestData(2)); + + readOnlySystem.Update (); + Assert.AreEqual (2, readOnlySystem.Group.Data[0].value); + Assert.Throws(()=> { readOnlySystem.Group.Data[0] = new EcsTestData(); }); + } + + + + [Test] + public void SharedComponentDataArray() + { + var sharedComponentSystem = World.GetOrCreateManager (); + + var go = m_Manager.CreateEntity(new ComponentType[0]); + m_Manager.AddComponentData (go, new EcsTestData(2)); + m_Manager.AddSharedComponentData(go, new SharedData(3)); + + sharedComponentSystem.Update (); + Assert.AreEqual (1, sharedComponentSystem.Group.Data.Length); + Assert.AreEqual (2, sharedComponentSystem.Group.Data[0].value); + Assert.AreEqual (3, sharedComponentSystem.Group.SharedData[0].value); + } + + + [Test] + public void RemoveComponentGroupTracking() + { + var pureSystem = World.GetOrCreateManager (); + + var go0 = m_Manager.CreateEntity (new ComponentType[0]); + m_Manager.AddComponentData (go0, new EcsTestData(10)); + + var go1 = m_Manager.CreateEntity (); + m_Manager.AddComponentData (go1, new EcsTestData(20)); + + pureSystem.Update (); + Assert.AreEqual (2, pureSystem.Group.Length); + Assert.AreEqual (10, pureSystem.Group.Data[0].value); + Assert.AreEqual (20, pureSystem.Group.Data[1].value); + + m_Manager.RemoveComponent (go0); + + pureSystem.Update (); + Assert.AreEqual (1, pureSystem.Group.Length); + Assert.AreEqual (20, pureSystem.Group.Data[0].value); + + m_Manager.RemoveComponent (go1); + pureSystem.Update (); + Assert.AreEqual (0, pureSystem.Group.Length); + } + + [Test] + public void EntityGroupTracking() + { + var pureSystem = World.GetOrCreateManager (); + + var go = m_Manager.CreateEntity (new ComponentType[0]); + m_Manager.AddComponentData (go, new EcsTestData(2)); + + pureSystem.Update (); + Assert.AreEqual (1, pureSystem.Group.Length); + Assert.AreEqual (1, pureSystem.Group.Data.Length); + Assert.AreEqual (1, pureSystem.Group.Entities.Length); + Assert.AreEqual (2, pureSystem.Group.Data[0].value); + Assert.AreEqual (go, pureSystem.Group.Entities[0]); + } + + [DisableAutoCreation] + public class FromEntitySystemIncrementInJob : JobComponentSystem + { + public struct IncrementValueJob : IJob + { + public Entity entity; + + public ComponentDataFromEntity ecsTestDataFromEntity; + public FixedArrayFromEntity intArrayFromEntity; + + public void Execute() + { + var array = intArrayFromEntity[entity]; + for (int i = 0;i intArrayFromEntity; + + [Inject] + ComponentDataFromEntity ecsTestDataFromEntity; + + public Entity entity; + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + var job = new IncrementValueJob(); + job.entity = entity; + job.ecsTestDataFromEntity = ecsTestDataFromEntity; + job.intArrayFromEntity = intArrayFromEntity; + + return job.Schedule(inputDeps); + } + } + + [Test] + public void FromEntitySystemIncrementInJobWorks() + { + var system = World.GetOrCreateManager (); + + var entity = m_Manager.CreateEntity (typeof(EcsTestData), ComponentType.FixedArray(typeof(int), 5)); + system.entity = entity; + system.Update(); + system.Update(); + + Assert.AreEqual(2, m_Manager.GetComponentData(entity).value); + Assert.AreEqual(2, m_Manager.GetFixedArray(entity)[0]); + } + + [DisableAutoCreation] + public class OnCreateManagerComponentGroupInjectionSystem : JobComponentSystem + { + public struct Group + { + public ComponentDataArray Data; + } + + [Inject] + public Group group; + + protected override void OnCreateManager(int capacity) + { + Assert.AreEqual(1, group.Data.Length); + Assert.AreEqual(42, group.Data[0].value); + } + } + + [Test] + public void OnCreateManagerComponentGroupInjectionWorks() + { + var entity = m_Manager.CreateEntity (typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + World.GetOrCreateManager(); + } + + [DisableAutoCreation] + public class OnDestroyManagerComponentGroupInjectionSystem : JobComponentSystem + { + public struct Group + { + public ComponentDataArray Data; + } + + [Inject] + public Group group; + + protected override void OnDestroyManager() + { + Assert.AreEqual(1, group.Data.Length); + Assert.AreEqual(42, group.Data[0].value); + } + } + + [Test] + public void OnDestroyManagerComponentGroupInjectionWorks() + { + var system = World.GetOrCreateManager(); + var entity = m_Manager.CreateEntity (typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + World.DestroyManager(system); + } + } +} diff --git a/Unity.Entities.Tests/InjectComponentGroupTests.cs.meta b/Unity.Entities.Tests/InjectComponentGroupTests.cs.meta new file mode 100644 index 00000000..111ecf8a --- /dev/null +++ b/Unity.Entities.Tests/InjectComponentGroupTests.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 31ecd4b002f954cbfb4ca96c1a1b9d20 +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/IterationTests.cs b/Unity.Entities.Tests/IterationTests.cs new file mode 100644 index 00000000..b74e6c44 --- /dev/null +++ b/Unity.Entities.Tests/IterationTests.cs @@ -0,0 +1,256 @@ +using NUnit.Framework; +using Unity.Collections; +using System.Collections.Generic; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class IterationTests : ECSTestsFixture + { + [Test] + public void CreateComponentGroup() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(EcsTestData2)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(0, arr.Length); + + var entity = m_Manager.CreateEntity(archetype); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + arr = group.GetComponentDataArray(); + Assert.AreEqual(1, arr.Length); + Assert.AreEqual(42, arr[0].value); + + m_Manager.DestroyEntity(entity); + } + + struct TempComponentNeverInstantiated : IComponentData + {} + [Test] + public void IterateEmptyArchetype() + { + var group = m_Manager.CreateComponentGroup(typeof(TempComponentNeverInstantiated)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(0, arr.Length); + + var archetype = m_Manager.CreateArchetype(typeof(TempComponentNeverInstantiated)); + arr = group.GetComponentDataArray(); + Assert.AreEqual(0, arr.Length); + + Entity ent = m_Manager.CreateEntity(archetype); + arr = group.GetComponentDataArray(); + Assert.AreEqual(1, arr.Length); + m_Manager.DestroyEntity(ent); + arr = group.GetComponentDataArray(); + Assert.AreEqual(0, arr.Length); + } + [Test] + public void IterateChunkedComponentGroup() + { + var archetype1 = m_Manager.CreateArchetype(typeof(EcsTestData)); + var archetype2 = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(0, arr.Length); + + Entity[] entities = new Entity[10000]; + for (int i = 0; i < entities.Length/2;i++) + { + entities[i] = m_Manager.CreateEntity(archetype1); + m_Manager.SetComponentData(entities[i], new EcsTestData(i)); + } + for (int i = entities.Length/2; i < entities.Length;i++) + { + entities[i] = m_Manager.CreateEntity(archetype2); + m_Manager.SetComponentData(entities[i], new EcsTestData(i)); + } + + arr = group.GetComponentDataArray(); + Assert.AreEqual(entities.Length, arr.Length); + HashSet values = new HashSet(); + for (int i = 0; i < arr.Length;i++) + { + int val = arr[i].value; + Assert.IsFalse(values.Contains(i)); + Assert.IsTrue(val >= 0); + Assert.IsTrue(val < entities.Length); + values.Add(i); + } + + for (int i = 0; i < entities.Length;i++) + m_Manager.DestroyEntity(entities[i]); + } + [Test] + public void IterateChunkedComponentGroupBackwards() + { + var archetype1 = m_Manager.CreateArchetype(typeof(EcsTestData)); + var archetype2 = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(0, arr.Length); + + Entity[] entities = new Entity[10000]; + for (int i = 0; i < entities.Length/2;i++) + { + entities[i] = m_Manager.CreateEntity(archetype1); + m_Manager.SetComponentData(entities[i], new EcsTestData(i)); + } + for (int i = entities.Length/2; i < entities.Length;i++) + { + entities[i] = m_Manager.CreateEntity(archetype2); + m_Manager.SetComponentData(entities[i], new EcsTestData(i)); + } + + arr = group.GetComponentDataArray(); + Assert.AreEqual(entities.Length, arr.Length); + HashSet values = new HashSet(); + for (int i = arr.Length-1; i >= 0;i--) + { + int val = arr[i].value; + Assert.IsFalse(values.Contains(i)); + Assert.IsTrue(val >= 0); + Assert.IsTrue(val < entities.Length); + values.Add(i); + } + + for (int i = 0; i < entities.Length;i++) + m_Manager.DestroyEntity(entities[i]); + } + + + + [Test] + public void IterateChunkedComponentGroupAfterDestroy() + { + var archetype1 = m_Manager.CreateArchetype(typeof(EcsTestData)); + var archetype2 = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + Assert.AreEqual(0, arr.Length); + + Entity[] entities = new Entity[10000]; + for (int i = 0; i < entities.Length/2;i++) + { + entities[i] = m_Manager.CreateEntity(archetype1); + m_Manager.SetComponentData(entities[i], new EcsTestData(i)); + } + for (int i = entities.Length/2; i < entities.Length;i++) + { + entities[i] = m_Manager.CreateEntity(archetype2); + m_Manager.SetComponentData(entities[i], new EcsTestData(i)); + } + for (int i = 0; i < entities.Length;i++) + { + if (i%2 != 0) + { + m_Manager.DestroyEntity(entities[i]); + } + } + + arr = group.GetComponentDataArray(); + Assert.AreEqual(entities.Length/2, arr.Length); + HashSet values = new HashSet(); + for (int i = 0; i < arr.Length;i++) + { + int val = arr[i].value; + Assert.IsFalse(values.Contains(i)); + Assert.IsTrue(val >= 0); + Assert.IsTrue(val%2 == 0); + Assert.IsTrue(val < entities.Length); + values.Add(i); + } + + for (int i = entities.Length/2; i < entities.Length;i++) + { + if (i%2 == 0) + m_Manager.RemoveComponent(entities[i]); + } + arr = group.GetComponentDataArray(); + Assert.AreEqual(entities.Length/4, arr.Length); + values = new HashSet(); + for (int i = 0; i < arr.Length;i++) + { + int val = arr[i].value; + Assert.IsFalse(values.Contains(i)); + Assert.IsTrue(val >= 0); + Assert.IsTrue(val%2 == 0); + Assert.IsTrue(val < entities.Length/2); + values.Add(i); + } + + for (int i = 0; i < entities.Length;i++) + { + if (i%2 == 0) + m_Manager.DestroyEntity(entities[i]); + } + } + + + [Test] + public void IterateEntityArray() + { + var archetype1 = m_Manager.CreateArchetype(typeof(EcsTestData)); + var archetype2 = m_Manager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetEntityArray(); + Assert.AreEqual(0, arr.Length); + + Entity[] entities = new Entity[10000]; + for (int i = 0; i < entities.Length/2;i++) + { + entities[i] = m_Manager.CreateEntity(archetype1); + m_Manager.SetComponentData(entities[i], new EcsTestData(i)); + } + for (int i = entities.Length/2; i < entities.Length;i++) + { + entities[i] = m_Manager.CreateEntity(archetype2); + m_Manager.SetComponentData(entities[i], new EcsTestData(i)); + } + + arr = group.GetEntityArray(); + Assert.AreEqual(entities.Length, arr.Length); + var values = new HashSet(); + for (int i = 0; i < arr.Length;i++) + { + Entity val = arr[i]; + Assert.IsFalse(values.Contains(val)); + values.Add(val); + } + + for (int i = 0; i < entities.Length;i++) + m_Manager.DestroyEntity(entities[i]); + } + + [Test] + public void ComponentDataArrayCopy() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + + var entities = new NativeArray(20000, Allocator.Persistent); + m_Manager.Instantiate(entity, entities); + + var ecsArray = m_Manager.CreateComponentGroup(typeof(EcsTestData)).GetComponentDataArray(); + + for (int i = 0; i < ecsArray.Length; i++) + ecsArray[i] = new EcsTestData(i); + + var copied = new NativeArray(entities.Length - 11 + 1, Allocator.Persistent); + ecsArray.CopyTo(copied, 11); + + for (int i = 0; i < copied.Length; i++) + { + if (copied[i].value != i) + Assert.AreEqual(i + 11, copied[i].value); + } + + copied.Dispose(); + entities.Dispose(); + } + + } +} \ No newline at end of file diff --git a/Unity.Entities.Tests/IterationTests.cs.meta b/Unity.Entities.Tests/IterationTests.cs.meta new file mode 100644 index 00000000..445565ee --- /dev/null +++ b/Unity.Entities.Tests/IterationTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1435824638e24699b96b39e2c8103d92 +timeCreated: 1510847146 \ No newline at end of file diff --git a/Unity.Entities.Tests/JobComponentSystemDependencyTests.cs b/Unity.Entities.Tests/JobComponentSystemDependencyTests.cs new file mode 100644 index 00000000..3d45014f --- /dev/null +++ b/Unity.Entities.Tests/JobComponentSystemDependencyTests.cs @@ -0,0 +1,221 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Unity.Entities.Tests +{ + public class JobComponentSystemDependencyTests : ECSTestsFixture + { + [DisableAutoCreation] + public class ReadSystem1 : JobComponentSystem + { + public struct Inputs + { + [ReadOnly] + public ComponentDataArray data; + } + + [Inject] Inputs m_Inputs; + + struct ReadJob : IJob + { + [ReadOnly] + public ComponentDataArray wat; + + public void Execute() + { + } + } + + protected override JobHandle OnUpdate(JobHandle input) + { + var job = new ReadJob() {wat = m_Inputs.data}; + return job.Schedule(input); + } + } + + [DisableAutoCreation] + public class ReadSystem2 : JobComponentSystem + { + public struct Inputs + { + [ReadOnly] + public ComponentDataArray data; + } + + public bool returnWrongJob = false; + public bool ignoreInputDeps = false; + + [Inject] private Inputs m_Inputs; + + private struct ReadJob : IJob + { + [ReadOnly] + public ComponentDataArray wat; + + public void Execute() + { + } + } + + protected override JobHandle OnUpdate(JobHandle input) + { + JobHandle h; + + var job = new ReadJob() {wat = m_Inputs.data}; + + if (ignoreInputDeps) + { + h = job.Schedule(); + } + else + { + h = job.Schedule(input); + } + + return returnWrongJob ? input : h; + } + } + + [DisableAutoCreation] + public class ReadSystem3 : JobComponentSystem + { + public struct Inputs + { + [ReadOnly] + public ComponentDataArray data; + } + +#pragma warning disable 0169 // "never used" warning + [Inject] private Inputs m_Inputs; +#pragma warning restore 0169 + + protected override JobHandle OnUpdate(JobHandle input) + { + return input; + } + } + + [DisableAutoCreation] + public class WriteSystem : JobComponentSystem + { + public struct Inputs + { + public ComponentDataArray data; + } + + [Inject] private Inputs m_Inputs; + + public bool SkipJob = false; + + private struct WriteJob : IJob + { + public ComponentDataArray data; + + public void Execute() + { + } + } + + protected override JobHandle OnUpdate(JobHandle input) + { + if (!SkipJob) + { + var job = new WriteJob() {data = m_Inputs.data}; + return job.Schedule(input); + } + else + { + return input; + } + } + } + + [Test] + public void ReturningWrongJobThrowsInCorrectSystemUpdate() + { + var entity = m_Manager.CreateEntity (typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + ReadSystem1 rs1 = World.GetOrCreateManager(); + ReadSystem2 rs2 = World.GetOrCreateManager(); + + rs2.returnWrongJob = true; + + rs1.Update(); + Assert.Throws(() => { rs2.Update(); }); + } + + [Test] + public void IgnoredInputDepsThrowsInCorrectSystemUpdate() + { + var entity = m_Manager.CreateEntity (typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + WriteSystem ws1 = World.GetOrCreateManager(); + ReadSystem2 rs2 = World.GetOrCreateManager(); + + rs2.ignoreInputDeps = true; + + ws1.Update(); + Assert.Throws(() => { rs2.Update(); }); + } + + [Test] + public void NotSchedulingWriteJobIsHarmless() + { + var entity = m_Manager.CreateEntity (typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + WriteSystem ws1 = World.GetOrCreateManager(); + + ws1.Update(); + ws1.SkipJob = true; + ws1.Update(); + } + + [Test] + public void NotUsingDataIsHarmless() + { + var entity = m_Manager.CreateEntity (typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + ReadSystem1 rs1 = World.GetOrCreateManager(); + ReadSystem3 rs3 = World.GetOrCreateManager(); + + rs1.Update(); + rs3.Update(); + } + + + [DisableAutoCreation] + class UseEcsTestDataFromEntity: JobComponentSystem + { + public struct MutateEcsTestDataJob : IJob + { + public ComponentDataFromEntity data; + + public void Execute() + { + + } + } + + protected override JobHandle OnUpdate(JobHandle dep) + { + var job = new MutateEcsTestDataJob { data = GetComponentDataFromEntity() }; + return job.Schedule(dep); + } + } + + // The writer dependency on EcsTestData is not predeclared during + // OnCreateManager, but we still expect the code to work correctly. + // This should result in a sync point when adding the dependency for the first time. + [Test] + public void AddingDependencyTypeDuringOnUpdateSyncsDependency() + { + var systemA = World.CreateManager(); + var systemB = World.CreateManager(); + + systemA.Update(); + systemB.Update(); + } + } +} diff --git a/Unity.Entities.Tests/JobComponentSystemDependencyTests.cs.meta b/Unity.Entities.Tests/JobComponentSystemDependencyTests.cs.meta new file mode 100644 index 00000000..d315ba47 --- /dev/null +++ b/Unity.Entities.Tests/JobComponentSystemDependencyTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d9b7fd5ce783457cabd562c3e6ffbe45 +timeCreated: 1512086612 \ No newline at end of file diff --git a/Unity.Entities.Tests/JobSafetyTests.cs b/Unity.Entities.Tests/JobSafetyTests.cs new file mode 100644 index 00000000..140938e5 --- /dev/null +++ b/Unity.Entities.Tests/JobSafetyTests.cs @@ -0,0 +1,140 @@ +using NUnit.Framework; +using Unity.Jobs; +using Unity.Entities; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Entities.Tests +{ + public class JobSafetyTests : ECSTestsFixture + { + public JobSafetyTests() + { + Assert.IsTrue(Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobDebuggerEnabled, "JobDebugger must be enabled for these tests"); + } + + struct TestIncrementJob : IJob + { + public ComponentDataArray data; + public void Execute() + { + for (int i = 0; i != data.Length; i++) + { + var d = data[i]; + d.value++; + data[i] = d; + } + } + } + + + + [Test] + public void ComponentAccessAfterScheduledJobThrows() + { + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + + var job = new TestIncrementJob(); + job.data = group.GetComponentDataArray(); + Assert.AreEqual(42, job.data[0].value); + + var fence = job.Schedule(); + + Assert.Throws(() => + { + var f = job.data[0].value; + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + f.GetHashCode(); + }); + + fence.Complete(); + Assert.AreEqual(43, job.data[0].value); + } + + [Test] + public void GetComponentCompletesJob() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + + var job = new TestIncrementJob(); + job.data = group.GetComponentDataArray(); + group.AddDependency(job.Schedule()); + + // Implicit Wait for job, returns value after job has completed. + Assert.AreEqual(1, m_Manager.GetComponentData(entity).value); + } + + [Test] + public void DestroyEntityCompletesScheduledJobs() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + /*var entity2 =*/ m_Manager.CreateEntity(typeof(EcsTestData)); + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + + var job = new TestIncrementJob(); + job.data = group.GetComponentDataArray(); + group.AddDependency(job.Schedule()); + + m_Manager.DestroyEntity(entity); + + // @TODO: This is maybe a little bit dodgy way of determining if the job has been completed... + // Probably should expose api to inspector job debugger state... + Assert.AreEqual(1, group.GetComponentDataArray().Length); + Assert.AreEqual(1, group.GetComponentDataArray()[0].value); + } + + [Test] + public void EntityManagerDestructionDetectsUnregisteredJob() + { + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex("job is still running")); + + /*var entity =*/ m_Manager.CreateEntity(typeof(EcsTestData)); + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + + var job = new TestIncrementJob(); + job.data = group.GetComponentDataArray(); + job.Schedule(); + + TearDown(); + } + + [Test] + public void DestroyEntityDetectsUnregisteredJob() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + + var job = new TestIncrementJob(); + job.data = group.GetComponentDataArray(); + var fence = job.Schedule(); + + Assert.Throws(() => { m_Manager.DestroyEntity(entity); }); + + fence.Complete(); + } + + [Test] + public void GetComponentDetectsUnregisteredJob() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + + var job = new TestIncrementJob(); + job.data = group.GetComponentDataArray(); + var jobHandle = job.Schedule(); + + Assert.Throws(() => { m_Manager.GetComponentData(entity); }); + + jobHandle.Complete(); + } + + [Test] + [Ignore("Should work, need to write test")] + public void TwoJobsAccessingEntityArrayCanRunInParallel() + { + } + } +} diff --git a/Unity.Entities.Tests/JobSafetyTests.cs.meta b/Unity.Entities.Tests/JobSafetyTests.cs.meta new file mode 100644 index 00000000..a962171a --- /dev/null +++ b/Unity.Entities.Tests/JobSafetyTests.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 827bca240b61d40c787c4026d674090c +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/MoveEntitiesFrom.cs b/Unity.Entities.Tests/MoveEntitiesFrom.cs new file mode 100644 index 00000000..496da557 --- /dev/null +++ b/Unity.Entities.Tests/MoveEntitiesFrom.cs @@ -0,0 +1,105 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class MoveEntitiesFrom : ECSTestsFixture + { + [Test] + public void MoveEntitiesToSameEntityManagerThrows() + { + Assert.Throws(() => { m_Manager.MoveEntitiesFrom(m_Manager); }); + } + + [Test] + public void MoveEntities() + { + var creationWorld = new World("CreationWorld"); + var creationManager = creationWorld.GetOrCreateManager(); + + var archetype = creationManager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2)); + + var entities = new NativeArray(10000, Allocator.Temp); + creationManager.CreateEntity(archetype, entities); + for (int i = 0; i != entities.Length; i++) + creationManager.SetComponentData(entities[i], new EcsTestData(i)); + + + m_Manager.CheckInternalConsistency(); + creationManager.CheckInternalConsistency(); + + m_Manager.MoveEntitiesFrom(creationManager); + + for (int i = 0;i != entities.Length;i++) + Assert.IsFalse(creationManager.Exists(entities[0])); + + m_Manager.CheckInternalConsistency(); + creationManager.CheckInternalConsistency(); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + Assert.AreEqual(entities.Length, group.CalculateLength()); + Assert.AreEqual(0, creationManager.CreateComponentGroup(typeof(EcsTestData)).CalculateLength()); + + // We expect that the order of the crated entities is the same as in the creation scene + var testDataArray = group.GetComponentDataArray(); + for (int i = 0; i != testDataArray.Length; i++) + Assert.AreEqual(i, testDataArray[i].value); + + entities.Dispose(); + creationWorld.Dispose(); + } + + [Test] + public void MoveEntitiesWithSharedComponentData() + { + var creationWorld = new World("CreationWorld"); + var creationManager = creationWorld.GetOrCreateManager(); + + var archetype = creationManager.CreateArchetype(typeof(EcsTestData), typeof(EcsTestData2), typeof(SharedData1)); + + var entities = new NativeArray(10000, Allocator.Temp); + creationManager.CreateEntity(archetype, entities); + for (int i = 0; i != entities.Length; i++) + { + creationManager.SetComponentData(entities[i], new EcsTestData(i)); + creationManager.SetSharedComponentData(entities[i], new SharedData1(i % 5)); + } + + m_Manager.CheckInternalConsistency(); + creationManager.CheckInternalConsistency(); + + m_Manager.MoveEntitiesFrom(creationManager); + + m_Manager.CheckInternalConsistency(); + creationManager.CheckInternalConsistency(); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData1)); + Assert.AreEqual(entities.Length, group.CalculateLength()); + Assert.AreEqual(0, creationManager.CreateComponentGroup(typeof(EcsTestData)).CalculateLength()); + + // We expect that the order of the crated entities is the same as in the creation scene + var testDataArray = group.GetComponentDataArray(); + var testSharedDataArray = group.GetSharedComponentDataArray(); + for (int i = 0;i != testDataArray.Length;i++) + Assert.AreEqual(testSharedDataArray[i].value, testDataArray[i].value % 5); + + entities.Dispose(); + creationWorld.Dispose(); + } + + [Test] + [Ignore("NOT IMPLEMENTED")] + public void MoveEntitiesPatchesEntityReferences() + { + + } + + [Test] + [Ignore("NOT IMPLEMENTED")] + public void UsingComponentGroupOrArchetypeorEntityFromDifferentEntityManagerGivesExceptions() + { + } + } +} diff --git a/Unity.Entities.Tests/MoveEntitiesFrom.cs.meta b/Unity.Entities.Tests/MoveEntitiesFrom.cs.meta new file mode 100644 index 00000000..531d3fa6 --- /dev/null +++ b/Unity.Entities.Tests/MoveEntitiesFrom.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c5078438451b4ffe8c88cd10014c6b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/NativeArraySharedValuesTests.cs b/Unity.Entities.Tests/NativeArraySharedValuesTests.cs new file mode 100644 index 00000000..a3ceb8f7 --- /dev/null +++ b/Unity.Entities.Tests/NativeArraySharedValuesTests.cs @@ -0,0 +1,134 @@ +using System.ComponentModel; +using NUnit.Framework; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Unity.Entities.Tests +{ + public class NativeArraySharedValuesTests + { + [Test] + public void NativeArraySharedValuesResultInOrderNoRemainder() + { + int count = 1024; + var source = new NativeArray(count, Allocator.TempJob); + for (int i = 0; i < count; i++) + { + source[i] = count - (i / 2); + } + var sharedValues = new NativeArraySharedValues(source, Allocator.TempJob); + var sharedValuesJobHandle = sharedValues.Schedule(default(JobHandle)); + sharedValuesJobHandle.Complete(); + var sortedIndices = sharedValues.GetSortedIndices(); + + var lastIndex = sortedIndices[0]; + var lastValue = source[lastIndex]; + + for (int i = 1; i < count; i++) + { + var index = sortedIndices[i]; + var value = source[index]; + + Assert.GreaterOrEqual(value,lastValue); + + lastIndex = index; + lastValue = value; + } + sharedValues.Dispose(); + source.Dispose(); + } + + [Test] + public void NativeArraySharedValuesResultInOrderLargeRemainder() + { + int count = 1024 + 1023; + var source = new NativeArray(count, Allocator.TempJob); + for (int i = 0; i < count; i++) + { + source[i] = count - (i / 2); + } + var sharedValues = new NativeArraySharedValues(source, Allocator.TempJob); + var sharedValuesJobHandle = sharedValues.Schedule(default(JobHandle)); + sharedValuesJobHandle.Complete(); + var sortedIndices = sharedValues.GetSortedIndices(); + + var lastIndex = sortedIndices[0]; + var lastValue = source[lastIndex]; + + for (int i = 1; i < count; i++) + { + var index = sortedIndices[i]; + var value = source[index]; + + Assert.GreaterOrEqual(value,lastValue); + + lastIndex = index; + lastValue = value; + } + sharedValues.Dispose(); + source.Dispose(); + } + + [Test] + public void NativeArraySharedValuesFoundAllValues() + { + int count = 1024 + 1023; + // int count = 32 + 31; + var source = new NativeArray(count, Allocator.TempJob); + for (int i = 0; i < count; i++) + { + source[i] = count - (i / 2); + } + var sharedValues = new NativeArraySharedValues(source, Allocator.TempJob); + var sharedValuesJobHandle = sharedValues.Schedule(default(JobHandle)); + sharedValuesJobHandle.Complete(); + + var sortedIndices = sharedValues.GetSortedIndices(); + for (int i = 0; i < count; i++) + { + var foundValue = false; + for (int j = 0; j < sortedIndices.Length; j++) + { + if (sortedIndices[j] == i) + { + foundValue = true; + break; + } + } + Assert.AreEqual(true, foundValue); + } + sharedValues.Dispose(); + source.Dispose(); + } + + [Test] + public void NativeArraySharedValuesSameValues() + { + int count = 1024 + 1023; + var source = new NativeArray(count, Allocator.TempJob); + for (int i = 0; i < count; i++) + { + source[i] = count - (i / 2); + } + var sharedValues = new NativeArraySharedValues(source, Allocator.TempJob); + var sharedValuesJobHandle = sharedValues.Schedule(default(JobHandle)); + sharedValuesJobHandle.Complete(); + + for (int i = 0; i < count; i++) + { + var sharedValueIndices = sharedValues.GetSharedValueIndicesBySourceIndex(i); + var sourceValue = source[i]; + Assert.GreaterOrEqual(sharedValueIndices.Length,1); + for (int j = 0; j < sharedValueIndices.Length; j++) + { + var otherIndex = sharedValueIndices[j]; + var otherValue = source[otherIndex]; + Assert.AreEqual(sourceValue,otherValue); + } + } + sharedValues.Dispose(); + source.Dispose(); + } + } +} diff --git a/Unity.Entities.Tests/NativeArraySharedValuesTests.cs.meta b/Unity.Entities.Tests/NativeArraySharedValuesTests.cs.meta new file mode 100644 index 00000000..c10a2664 --- /dev/null +++ b/Unity.Entities.Tests/NativeArraySharedValuesTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: caa1489db26fec0409d68c1032ce4b7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/SafetyTests.cs b/Unity.Entities.Tests/SafetyTests.cs new file mode 100644 index 00000000..c3d56563 --- /dev/null +++ b/Unity.Entities.Tests/SafetyTests.cs @@ -0,0 +1,175 @@ +using NUnit.Framework; +using System; +using Unity.Entities; + +//@TODO: We should really design systems / jobs / exceptions / errors +// so that an error in one system does not affect the next system. +// Right now failure to set dependencies correctly in one system affects other code, +// this makes the error messages significantly less useful... +// So need to redo all tests accordingly + +namespace Unity.Entities.Tests +{ + public class SafetyTests : ECSTestsFixture + { + + [Test] + public void RemoveEntityComponentThrows() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + Assert.Throws(() => { m_Manager.RemoveComponent(entity, typeof(Entity)); }); + Assert.IsTrue(m_Manager.HasComponent(entity)); + } + + [Test] + public void ComponentArrayChunkSliceOutOfBoundsThrowsException() + { + for (int i = 0;i<10;i++) + m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var testData = group.GetComponentDataArray(); + + Assert.AreEqual(0, testData.GetChunkArray(5, 0).Length); + Assert.AreEqual(10, testData.GetChunkArray(0, 10).Length); + + Assert.Throws(() => { testData.GetChunkArray(-1, 1); }); + Assert.Throws(() => { testData.GetChunkArray(5, 6); }); + Assert.Throws(() => { testData.GetChunkArray(10, 1); }); + } + + + [Test] + public void ReadOnlyComponentDataArray() + { + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData2), ComponentType.ReadOnly(typeof(EcsTestData))); + + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + m_Manager.SetComponentData(entity, new EcsTestData(42)); + + // EcsTestData is read only + var arr = group.GetComponentDataArray(); + Assert.AreEqual(1, arr.Length); + Assert.AreEqual(42, arr[0].value); + Assert.Throws(() => { arr[0] = new EcsTestData(0); }); + + // EcsTestData2 can be written to + var arr2 = group.GetComponentDataArray(); + Assert.AreEqual(1, arr2.Length); + arr2[0] = new EcsTestData2(55); + Assert.AreEqual(55, arr2[0].value0); + } + + [Test] + public void AccessComponentArrayAfterCreationThrowsException() + { + CreateEntityWithDefaultData(0); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + + CreateEntityWithDefaultData(1); + + Assert.Throws(() => { var value = arr[0]; }); + } + + [Test] + public void CreateEntityInvalidatesArray() + { + CreateEntityWithDefaultData(0); + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData)); + var arr = group.GetComponentDataArray(); + + CreateEntityWithDefaultData(1); + + Assert.Throws(() => { var value = arr[0]; }); + } + + [Test] + public void GetSetComponentThrowsIfNotExist() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + var destroyedEntity = m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.DestroyEntity(destroyedEntity); + + Assert.Throws(() => { m_Manager.SetComponentData(entity, new EcsTestData2()); }); + Assert.Throws(() => { m_Manager.SetComponentData(destroyedEntity, new EcsTestData2()); }); + + Assert.Throws(() => { m_Manager.GetComponentData(entity); }); + Assert.Throws(() => { m_Manager.GetComponentData(destroyedEntity); }); + } + + [Test] + public void ComponentDataArrayFromEntityThrowsIfNotExist() + { + var entity = m_Manager.CreateEntity(typeof(EcsTestData)); + var destroyedEntity = m_Manager.CreateEntity(typeof(EcsTestData)); + m_Manager.DestroyEntity(destroyedEntity); + + var data = EmptySystem.GetComponentDataFromEntity(); + + Assert.Throws(() => { data[entity] = new EcsTestData2(); }); + Assert.Throws(() => { data[destroyedEntity] = new EcsTestData2(); }); + + Assert.Throws(() => { var p = data[entity]; }); + Assert.Throws(() => { var p = data[destroyedEntity]; }); + } + + [Test] + public void AddComponentTwiceThrows() + { + var entity = m_Manager.CreateEntity(); + + m_Manager.AddComponentData(entity, new EcsTestData(1)); + Assert.Throws(() => { m_Manager.AddComponentData(entity, new EcsTestData(1)); }); + } + + [Test] + public void AddRemoveComponentOnDestroyedEntityThrows() + { + var destroyedEntity = m_Manager.CreateEntity(); + m_Manager.DestroyEntity(destroyedEntity); + + Assert.Throws(() => { m_Manager.AddComponentData(destroyedEntity, new EcsTestData(1)); }); + Assert.Throws(() => { m_Manager.RemoveComponent(destroyedEntity); }); + } + + [Test] + public void RemoveComponentOnEntityWithoutComponent() + { + var entity = m_Manager.CreateEntity(); + Assert.Throws(() => { m_Manager.RemoveComponent(entity); }); + } + + [Test] + public void CreateDestroyEmptyEntity() + { + var entity = m_Manager.CreateEntity(); + Assert.IsTrue(m_Manager.Exists(entity)); + m_Manager.DestroyEntity(entity); + Assert.IsFalse(m_Manager.Exists(entity)); + } + + [Test] + public void CreateEntityWithNullTypeThrows() + { + Assert.Throws(() => m_Manager.CreateEntity(null)); + } + + [Test] + public void CreateEntityWithOneNullTypeThrows() + { + Assert.Throws(() => m_Manager.CreateEntity(null, typeof(EcsTestData))); + } + + [Test] + public void CreateTooBigArchetypeThrows() + { + Assert.Throws(() => + { + m_Manager.CreateArchetype(ComponentType.FixedArray(typeof(int), 10000), ComponentType.FixedArray(typeof(float), 10000)); + }); + } + } +} \ No newline at end of file diff --git a/Unity.Entities.Tests/SafetyTests.cs.meta b/Unity.Entities.Tests/SafetyTests.cs.meta new file mode 100644 index 00000000..0fb6ed79 --- /dev/null +++ b/Unity.Entities.Tests/SafetyTests.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 0b246b93c3a5b44368cfb72ece7ba9a1 +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/SharedComponentDataTests.cs b/Unity.Entities.Tests/SharedComponentDataTests.cs new file mode 100644 index 00000000..706669b1 --- /dev/null +++ b/Unity.Entities.Tests/SharedComponentDataTests.cs @@ -0,0 +1,302 @@ +using System; +using NUnit.Framework; +using System.Collections.Generic; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + struct SharedData1 : ISharedComponentData + { + public int value; + + public SharedData1(int val) { value = val; } + } + + struct SharedData2 : ISharedComponentData + { + public int value; + + public SharedData2(int val) { value = val; } + } + + public class SharedComponentDataTests : ECSTestsFixture + { + //@TODO: No tests for invalid shared components / destroyed shared component data + //@TODO: No tests for if we leak shared data when last entity is destroyed... + //@TODO: No tests for invalid shared component type? + + [Test] + public void SetSharedComponent() + { + var archetype = m_Manager.CreateArchetype(typeof(SharedData1), typeof(EcsTestData), typeof(SharedData2)); + + var group1 = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData1)); + var group2 = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData2)); + var group12 = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData2), typeof(SharedData1)); + + var group1_filter_0 = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData1)); + group1_filter_0.SetFilter(new SharedData1(0)); + var group1_filter_20 = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData1)); + group1_filter_20.SetFilter(new SharedData1(20)); + + Assert.AreEqual(0, group1.CalculateLength()); + Assert.AreEqual(0, group2.CalculateLength()); + Assert.AreEqual(0, group12.CalculateLength()); + + Assert.AreEqual(0, group1_filter_0.CalculateLength()); + Assert.AreEqual(0, group1_filter_20.CalculateLength()); + + Entity e1 = m_Manager.CreateEntity(archetype); + m_Manager.SetComponentData(e1, new EcsTestData(117)); + Entity e2 = m_Manager.CreateEntity(archetype); + m_Manager.SetComponentData(e2, new EcsTestData(243)); + + Assert.AreEqual(2, group1_filter_0.CalculateLength()); + Assert.AreEqual(0, group1_filter_20.CalculateLength()); + Assert.AreEqual(117, group1_filter_0.GetComponentDataArray()[0].value); + Assert.AreEqual(243, group1_filter_0.GetComponentDataArray()[1].value); + + m_Manager.SetSharedComponentData(e1, new SharedData1(20)); + + Assert.AreEqual(1, group1_filter_0.CalculateLength()); + Assert.AreEqual(1, group1_filter_20.CalculateLength()); + Assert.AreEqual(117, group1_filter_20.GetComponentDataArray()[0].value); + Assert.AreEqual(243, group1_filter_0.GetComponentDataArray()[0].value); + + m_Manager.SetSharedComponentData(e2, new SharedData1(20)); + + Assert.AreEqual(0, group1_filter_0.CalculateLength()); + Assert.AreEqual(2, group1_filter_20.CalculateLength()); + Assert.AreEqual(117, group1_filter_20.GetComponentDataArray()[0].value); + Assert.AreEqual(243, group1_filter_20.GetComponentDataArray()[1].value); + + group1.Dispose(); + group2.Dispose(); + group12.Dispose(); + group1_filter_0.Dispose(); + group1_filter_20.Dispose(); + } + + + [Test] + public void GetComponentArray() + { + var archetype1 = m_Manager.CreateArchetype(typeof(SharedData1), typeof(EcsTestData)); + var archetype2 = m_Manager.CreateArchetype(typeof(SharedData1), typeof(EcsTestData), typeof(SharedData2)); + + const int entitiesPerValue = 5000; + for (int i = 0; i < entitiesPerValue*8; ++i) + { + Entity e = m_Manager.CreateEntity((i % 2 == 0) ? archetype1 : archetype2); + m_Manager.SetComponentData(e, new EcsTestData(i)); + m_Manager.SetSharedComponentData(e, new SharedData1(i%8)); + } + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData1)); + + for (int sharedValue = 0; sharedValue < 8; ++sharedValue) + { + bool[] foundEntities = new bool[entitiesPerValue]; + group.SetFilter(new SharedData1(sharedValue)); + var componentArray = group.GetComponentDataArray(); + Assert.AreEqual(entitiesPerValue, componentArray.Length); + for (int i = 0; i < entitiesPerValue; ++i) + { + int index = componentArray[i].value; + Assert.AreEqual(sharedValue, index % 8); + Assert.IsFalse(foundEntities[index/8]); + foundEntities[index/8] = true; + } + } + + group.Dispose(); + } + + [Test] + public void GetAllUniqueSharedComponents() + { + var unique = new List(0); + m_Manager.GetAllUniqueSharedComponentDatas(unique); + + Assert.AreEqual(1, unique.Count); + Assert.AreEqual(default(SharedData1).value, unique[0].value); + + var archetype = m_Manager.CreateArchetype(typeof(SharedData1), typeof(EcsTestData)); + Entity e = m_Manager.CreateEntity(archetype); + m_Manager.SetSharedComponentData(e, new SharedData1(17)); + + unique.Clear(); + m_Manager.GetAllUniqueSharedComponentDatas(unique); + + Assert.AreEqual(2, unique.Count); + Assert.AreEqual(default(SharedData1).value, unique[0].value); + Assert.AreEqual(17, unique[1].value); + + m_Manager.SetSharedComponentData(e, new SharedData1(34)); + + unique.Clear(); + m_Manager.GetAllUniqueSharedComponentDatas(unique); + + Assert.AreEqual(2, unique.Count); + Assert.AreEqual(default(SharedData1).value, unique[0].value); + Assert.AreEqual(34, unique[1].value); + + m_Manager.DestroyEntity(e); + + unique.Clear(); + m_Manager.GetAllUniqueSharedComponentDatas(unique); + + Assert.AreEqual(1, unique.Count); + Assert.AreEqual(default(SharedData1).value, unique[0].value); + } + + [Test] + public void CreateForEachFilter() + { + var archetype1 = m_Manager.CreateArchetype(typeof(SharedData1), typeof(EcsTestData)); + + const int unqueValues = 500; + const int entitiesPerValue = 100; + for (int i = 0; i < unqueValues; ++i) + { + for (int j = 0; j < entitiesPerValue; ++j) + { + Entity e = m_Manager.CreateEntity(archetype1); + m_Manager.SetComponentData(e, new EcsTestData((i << 16) + j)); + m_Manager.SetSharedComponentData(e, new SharedData1(i)); + } + } + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData1)); + + var unique = new List(0); + m_Manager.GetAllUniqueSharedComponentDatas(unique); + Assert.AreEqual(unqueValues, unique.Count); + var forEachFilter = group.CreateForEachFilter(unique); + + for (int sharedValue = 0; sharedValue < unique.Count; ++sharedValue) + { + bool[] foundEntities = new bool[entitiesPerValue]; + var componentArray = group.GetComponentDataArray(forEachFilter, sharedValue); + Assert.AreEqual(entitiesPerValue, componentArray.Length); + for (int i = 0; i < entitiesPerValue; ++i) + { + int index = componentArray[i].value; + Assert.AreEqual(sharedValue, index>>16); + Assert.IsFalse(foundEntities[index&0xffff]); + foundEntities[index&0xffff] = true; + } + } + + forEachFilter.Dispose(); + group.Dispose(); + } + + [Test] + public void GetSharedComponentData() + { + var archetype = m_Manager.CreateArchetype(typeof(SharedData1), typeof(EcsTestData)); + Entity e = m_Manager.CreateEntity(archetype); + + Assert.AreEqual(0, m_Manager.GetSharedComponentData(e).value); + + m_Manager.SetSharedComponentData(e, new SharedData1(17)); + + Assert.AreEqual(17, m_Manager.GetSharedComponentData(e).value); + } + + [Test] + public void NonExistingSharedComponentDataThrows() + { + Entity e = m_Manager.CreateEntity(typeof(EcsTestData)); + + Assert.Throws(() => { m_Manager.GetSharedComponentData(e); }); + Assert.Throws(() => { m_Manager.SetSharedComponentData(e, new SharedData1()); }); + } + + [Test] + public void AddSharedComponent() + { + var archetype = m_Manager.CreateArchetype(typeof(EcsTestData)); + Entity e = m_Manager.CreateEntity(archetype); + + Assert.IsFalse(m_Manager.HasComponent(e)); + Assert.IsFalse(m_Manager.HasComponent(e)); + + m_Manager.AddSharedComponentData(e, new SharedData1(17)); + + Assert.IsTrue(m_Manager.HasComponent(e)); + Assert.IsFalse(m_Manager.HasComponent(e)); + Assert.AreEqual(17, m_Manager.GetSharedComponentData(e).value); + + m_Manager.AddSharedComponentData(e, new SharedData2(34)); + Assert.IsTrue(m_Manager.HasComponent(e)); + Assert.IsTrue(m_Manager.HasComponent(e)); + Assert.AreEqual(17, m_Manager.GetSharedComponentData(e).value); + Assert.AreEqual(34, m_Manager.GetSharedComponentData(e).value); + } + + [Test] + public void RemoveSharedComponent() + { + Entity e = m_Manager.CreateEntity(); + + m_Manager.AddComponentData(e, new EcsTestData(42)); + m_Manager.AddSharedComponentData(e, new SharedData1(17)); + m_Manager.AddSharedComponentData(e, new SharedData2(34)); + + Assert.IsTrue(m_Manager.HasComponent(e)); + Assert.IsTrue(m_Manager.HasComponent(e)); + Assert.AreEqual(17, m_Manager.GetSharedComponentData(e).value); + Assert.AreEqual(34, m_Manager.GetSharedComponentData(e).value); + + m_Manager.RemoveComponent(e); + Assert.IsFalse(m_Manager.HasComponent(e)); + Assert.AreEqual(34, m_Manager.GetSharedComponentData(e).value); + + m_Manager.RemoveComponent(e); + Assert.IsFalse(m_Manager.HasComponent(e)); + + Assert.AreEqual(42, m_Manager.GetComponentData(e).value); + } + + [Test] + public void GetSharedComponentArray() + { + var archetype1 = m_Manager.CreateArchetype(typeof(SharedData1), typeof(EcsTestData)); + var archetype2 = m_Manager.CreateArchetype(typeof(SharedData1), typeof(EcsTestData), typeof(SharedData2)); + + const int entitiesPerValue = 5000; + for (int i = 0; i < entitiesPerValue*8; ++i) + { + Entity e = m_Manager.CreateEntity((i % 2 == 0) ? archetype1 : archetype2); + m_Manager.SetComponentData(e, new EcsTestData(i)); + m_Manager.SetSharedComponentData(e, new SharedData1(i%8)); + } + + var group = m_Manager.CreateComponentGroup(typeof(EcsTestData), typeof(SharedData1)); + + var foundEntities = new bool[8, entitiesPerValue]; + + + var sharedComponentDataArray = group.GetSharedComponentDataArray(); + var componentArray = group.GetComponentDataArray(); + + Assert.AreEqual(entitiesPerValue*8, sharedComponentDataArray.Length); + Assert.AreEqual(entitiesPerValue*8, componentArray.Length); + + for (int i = 0; i < entitiesPerValue*8; ++i) + { + var sharedValue = sharedComponentDataArray[i].value; + int index = componentArray[i].value; + Assert.AreEqual(sharedValue, index % 8); + Assert.IsFalse(foundEntities[sharedValue, index/8]); + foundEntities[sharedValue, index/8] = true; + } + + group.Dispose(); + + } + + } +} diff --git a/Unity.Entities.Tests/SharedComponentDataTests.cs.meta b/Unity.Entities.Tests/SharedComponentDataTests.cs.meta new file mode 100644 index 00000000..2f30ff0f --- /dev/null +++ b/Unity.Entities.Tests/SharedComponentDataTests.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: fdb677eea31e74397997747f32007c6f +timeCreated: 1506188352 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/SystemStateComponentTests.cs b/Unity.Entities.Tests/SystemStateComponentTests.cs new file mode 100644 index 00000000..8bc8e334 --- /dev/null +++ b/Unity.Entities.Tests/SystemStateComponentTests.cs @@ -0,0 +1,271 @@ +using NUnit.Framework; +using Unity.Collections; +using System; +using NUnit.Framework.Interfaces; + +namespace Unity.Entities.Tests +{ + [TestFixture] + public class SystemStateComponentTests : ECSTestsFixture + { + [Test] + public void SSC_NoRemoveWithSystemStateComponentData() + { + var entity = m_Manager.CreateEntity( + typeof(EcsTestData), + typeof(EcsTestSharedComp), + typeof(EcsState1) + ); + + m_Manager.SetComponentData(entity, new EcsTestData(1)); + m_Manager.SetComponentData(entity, new EcsState1(2)); + m_Manager.SetSharedComponentData(entity, new EcsTestSharedComp(3)); + + Assert.Throws(() => { m_Manager.RemoveComponent(entity); }); + } + + [Test] + public void SSC_NoRemoveSystemStateWithComponentData() + { + var entity = m_Manager.CreateEntity( + typeof(EcsTestData), + typeof(EcsTestSharedComp), + typeof(EcsState1) + ); + + m_Manager.SetComponentData(entity, new EcsTestData(1)); + m_Manager.SetComponentData(entity, new EcsState1(2)); + m_Manager.SetSharedComponentData(entity, new EcsTestSharedComp(3)); + + Assert.Throws(() => { m_Manager.RemoveSystemStateComponent(entity); }); + } + + [Test] + public void SSC_DeleteWhenEmpty() + { + var entity = m_Manager.CreateEntity( + typeof(EcsTestData), + typeof(EcsTestSharedComp), + typeof(EcsState1) + ); + + m_Manager.SetComponentData(entity, new EcsTestData(1)); + m_Manager.SetComponentData(entity, new EcsState1(2)); + m_Manager.SetSharedComponentData(entity, new EcsTestSharedComp(3)); + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsTestData)}, // all + Allocator.Temp); + Assert.AreEqual(1, chunks.EntityCount); + chunks.Dispose(); + } + + m_Manager.DestroyEntity(entity); + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsTestData)}, // all + Allocator.Temp); + Assert.AreEqual(0, chunks.EntityCount); + chunks.Dispose(); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsState1)}, // all + Allocator.Temp); + Assert.AreEqual(1, chunks.EntityCount); + chunks.Dispose(); + } + + m_Manager.RemoveSystemStateComponent(entity); + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsState1)}, // all + Allocator.Temp); + Assert.AreEqual(0, chunks.EntityCount); + chunks.Dispose(); + } + + Assert.IsFalse(m_Manager.Exists(entity)); + } + + [Test] + public void SSC_DeleteWhenEmptyArray() + { + var entities = new Entity[512]; + + for (var i = 0; i < 512; i++) + { + var entity = m_Manager.CreateEntity( + typeof(EcsTestData), + typeof(EcsTestSharedComp), + typeof(EcsState1) + ); + entities[i] = entity; + + m_Manager.SetComponentData(entity, new EcsTestData(i)); + m_Manager.SetComponentData(entity, new EcsState1(i)); + m_Manager.SetSharedComponentData(entity, new EcsTestSharedComp(i % 7)); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsTestData)}, // all + Allocator.Temp); + Assert.AreEqual(512, chunks.EntityCount); + chunks.Dispose(); + } + + for (var i = 0; i < 512; i += 2) + { + var entity = entities[i]; + m_Manager.DestroyEntity(entity); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsTestData)}, // all + Allocator.Temp); + Assert.AreEqual(256, chunks.EntityCount); + chunks.Dispose(); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + new ComponentType[] {typeof(EcsTestData)}, // none + new ComponentType[] {typeof(EcsState1)}, // all + Allocator.Temp); + Assert.AreEqual(256, chunks.EntityCount); + chunks.Dispose(); + } + + for (var i = 0; i < 512; i += 2) + { + var entity = entities[i]; + m_Manager.RemoveSystemStateComponent(entity); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // none + Array.Empty(), // none + new ComponentType[] {typeof(EcsState1)}, // all + Allocator.Temp); + Assert.AreEqual(256, chunks.EntityCount); + chunks.Dispose(); + } + + for (var i = 0; i < 512; i += 2) + { + var entity = entities[i]; + Assert.IsFalse(m_Manager.Exists(entity)); + } + + for (var i = 1; i < 512; i += 2) + { + var entity = entities[i]; + Assert.IsTrue(m_Manager.Exists(entity)); + } + } + + [Test] + public void SSC_DeleteWhenEmptyArray2() + { + var entities = new Entity[512]; + + for (var i = 0; i < 512; i++) + { + var entity = m_Manager.CreateEntity( + typeof(EcsTestData), + typeof(EcsTestSharedComp), + typeof(EcsState1) + ); + entities[i] = entity; + + m_Manager.SetComponentData(entity, new EcsTestData(i)); + m_Manager.SetComponentData(entity, new EcsState1(i)); + m_Manager.SetSharedComponentData(entity, new EcsTestSharedComp(i % 7)); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsTestData)}, // all + Allocator.Temp); + Assert.AreEqual(512, chunks.EntityCount); + chunks.Dispose(); + } + + for (var i = 0; i < 256; i++) + { + var entity = entities[i]; + m_Manager.DestroyEntity(entity); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(EcsTestData)}, // all + Allocator.Temp); + Assert.AreEqual(256, chunks.EntityCount); + chunks.Dispose(); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + new ComponentType[] {typeof(EcsTestData)}, // none + new ComponentType[] {typeof(EcsState1)}, // all + Allocator.Temp); + Assert.AreEqual(256, chunks.EntityCount); + chunks.Dispose(); + } + + for (var i = 0; i < 256; i++) + { + var entity = entities[i]; + m_Manager.RemoveSystemStateComponent(entity); + } + + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // none + Array.Empty(), // none + new ComponentType[] {typeof(EcsState1)}, // all + Allocator.Temp); + Assert.AreEqual(256, chunks.EntityCount); + chunks.Dispose(); + } + + for (var i = 0; i < 256; i++) + { + var entity = entities[i]; + Assert.IsFalse(m_Manager.Exists(entity)); + } + + for (var i = 256; i < 512; i++) + { + var entity = entities[i]; + Assert.IsTrue(m_Manager.Exists(entity)); + } + } + } +} diff --git a/Unity.Entities.Tests/SystemStateComponentTests.cs.meta b/Unity.Entities.Tests/SystemStateComponentTests.cs.meta new file mode 100644 index 00000000..51b10e5e --- /dev/null +++ b/Unity.Entities.Tests/SystemStateComponentTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a584e7dbf0f54b539d63dad61115df9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/TestComponents.cs b/Unity.Entities.Tests/TestComponents.cs new file mode 100644 index 00000000..d58462df --- /dev/null +++ b/Unity.Entities.Tests/TestComponents.cs @@ -0,0 +1,60 @@ +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public struct EcsTestData : IComponentData + { + public int value; + + public EcsTestData(int inValue) { value = inValue; } + } + + public struct EcsTestData2 : IComponentData + { + public int value0; + public int value1; + + public EcsTestData2(int inValue) { value1 = value0 = inValue; } + } + + public struct EcsTestData3 : IComponentData + { + public int value0; + public int value1; + public int value2; + + public EcsTestData3(int inValue) { value2 = value1 = value0 = inValue; } + } + + public struct EcsTestData4 : IComponentData + { + public int value0; + public int value1; + public int value2; + public int value3; + + public EcsTestData4(int inValue) { value3 = value2 = value1 = value0 = inValue; } + } + + public struct EcsTestSharedComp : ISharedComponentData + { + public int value; + + public EcsTestSharedComp(int inValue) { value = inValue; } + } + + public struct EcsTestSharedComp2 : ISharedComponentData + { + public int value0; + public int value1; + + public EcsTestSharedComp2(int inValue) { value0 = value1 = inValue; } + } + + struct EcsState1 : ISystemStateComponentData + { + public int Value; + + public EcsState1(int value) { Value = value; } + } +} diff --git a/Unity.Entities.Tests/TestComponents.cs.meta b/Unity.Entities.Tests/TestComponents.cs.meta new file mode 100644 index 00000000..5702aaad --- /dev/null +++ b/Unity.Entities.Tests/TestComponents.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 23a4d38513cf547b483b308e7f932ff0 +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/TransformUpdateTests.cs b/Unity.Entities.Tests/TransformUpdateTests.cs new file mode 100644 index 00000000..73751583 --- /dev/null +++ b/Unity.Entities.Tests/TransformUpdateTests.cs @@ -0,0 +1,820 @@ +using System; +using NUnit.Framework; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Unity.Entities.Tests +{ + [TestFixture] + public class TransformUpdateTests : ECSTestsFixture + { + public interface IComponentMatrixData + { + float4x4 Value { get; set; } + } + + struct ParentTransform : IComponentData + { + public Entity Value; + } + + struct LocalToWorldMatrix : IComponentData, IComponentMatrixData + { + public float4x4 Value { get; set; } + } + + struct Position : IComponentData + { + public float3 Value; + } + + struct Heading : IComponentData + { + public float3 Value; + } + + struct Rotation : IComponentData + { + public quaternion Value; + } + + struct Scale : IComponentData + { + public float3 Value; + } + + struct UniformScale : IComponentData + { + public float Value; + } + + // + // Managed by system: + // + + // Change system does not provide previous value, so need to store. + private struct PreviousParentTransform : ISystemStateComponentData + { + public Entity Value; + } + + private struct TransformRoot : ISystemStateComponentData + { + public Entity Value; + } + + private struct LocalToParentMatrix : IComponentMatrixData, ISystemStateComponentData + { + public float4x4 Value { get; set; } + } + + [DisableAutoCreation] + [ComponentSystemPatch] + public class TransformPatch : JobComponentSystem + { + private uint LastSystemVersion = 0; + private int LastPositionVersion = 0; + private int LastRotationVersion = 0; + private int LastHeadingVersion = 0; + private int LastScaleVersion = 0; + private int LastUniformScaleVersion = 0; + private int LastParentTransformVersion = 0; + + private NativeQueue RootChangedQueue; + private NativeMultiHashMap TransformChildHashMap; + private ComponentDataFromEntity LocalToWorldMatrices; + private ComponentDataFromEntity LocalToParentMatrices; + private EntityCommandBuffer PostUpdateCommands; + + public TransformPatch() + { + TransformChildHashMap = new NativeMultiHashMap(1024, Allocator.Persistent); + RootChangedQueue = new NativeQueue(Allocator.Persistent); + } + + public void UpdateAddedParentTransforms() + { + var chunks = m_EntityManager.CreateArchetypeChunkArray( + Array.Empty(), // any + new ComponentType[] {typeof(PreviousParentTransform)}, // none + new ComponentType[] {typeof(ParentTransform)}, // all + Allocator.Temp); + + if (chunks.Length == 0) + { + chunks.Dispose(); + return; + } + + var transformRoots = EntityManager.GetComponentDataFromEntity(); + var transformParentType = EntityManager.GetArchetypeChunkComponentType(true); + var entityType = EntityManager.GetArchetypeChunkEntityType(true); + + Debug.Log(string.Format("New ParentTransform = {0}", chunks.EntityCount)); + + for (int chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++) + { + var chunk = chunks[chunkIndex]; + var parentCount = chunk.Count; + + var chunkParentTransforms = chunk.GetNativeArray(transformParentType); + var chunkEntities = chunk.GetNativeArray(entityType); + + for (int i = 0; i < parentCount; i++) + { + var childEntity = chunkEntities[i]; + var parentEntity = chunkParentTransforms[i].Value; + var rootEntity = parentEntity; + + if (transformRoots.Exists(rootEntity)) + { + rootEntity = transformRoots[rootEntity].Value; + } + + RootChangedQueue.Enqueue(rootEntity); + PostUpdateCommands.AddComponent(childEntity, new PreviousParentTransform()); + PostUpdateCommands.AddComponent(childEntity, new TransformRoot {Value = rootEntity}); + + // Separately call SetComponent so that change is tracked. + PostUpdateCommands.SetComponent(childEntity, + new PreviousParentTransform {Value = parentEntity}); + TransformChildHashMap.Add(parentEntity, childEntity); + } + } + + chunks.Dispose(); + } + + public void UpdateChangedParentTransforms() + { + var chunks = EntityManager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(ParentTransform), typeof(PreviousParentTransform)}, // all + Allocator.Temp); + + if (chunks.Length == 0) + { + chunks.Dispose(); + return; + } + + var transformRoots = EntityManager.GetComponentDataFromEntity(); + var transformParentType = EntityManager.GetArchetypeChunkComponentType(true); + var transformPreviousParentType = + EntityManager.GetArchetypeChunkComponentType(true); + var entityType = EntityManager.GetArchetypeChunkEntityType(true); + + for (int chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++) + { + var chunk = chunks[chunkIndex]; + + if (chunk.GetComponentVersion(transformParentType) <= GlobalSystemVersion) + continue; + + var childCount = chunk.Count; + var chunkParentTransforms = chunk.GetNativeArray(transformParentType); + var chunkPreviousParentTransforms = chunk.GetNativeArray(transformPreviousParentType); + var chunkEntities = chunk.GetNativeArray(entityType); + + for (int i = 0; i < childCount; i++) + { + var childEntity = chunkEntities[i]; + var parentEntity = chunkParentTransforms[i].Value; + var previousParentEntity = chunkPreviousParentTransforms[i].Value; + + UpdateParent(transformRoots, parentEntity, previousParentEntity, childEntity); + } + } + + chunks.Dispose(); + } + + private void RemoveParentChildMap(Entity parentEntity, Entity childEntity) + { + NativeMultiHashMapIterator it; + Entity foundChild; + if (!TransformChildHashMap.TryGetFirstValue(parentEntity, out foundChild, out it)) + { + throw new System.InvalidOperationException( + string.Format("ParentTransform not found in Hierarchy hashmap")); + } + + do + { + if (foundChild == childEntity) + { + TransformChildHashMap.Remove(it); + return; + } + } while (TransformChildHashMap.TryGetNextValue(out foundChild, ref it)); + + throw new System.InvalidOperationException( + string.Format("ParentTransform not found in Hierarchy hashmap")); + } + + private void UpdateParent(ComponentDataFromEntity transformRoots, Entity parentEntity, + Entity previousParentEntity, Entity childEntity) + { + if (parentEntity == previousParentEntity) + return; + + RemoveParentChildMap(previousParentEntity, childEntity); + + var rootEntity = parentEntity; + if (transformRoots.Exists(rootEntity)) + { + rootEntity = transformRoots[rootEntity].Value; + } + + RootChangedQueue.Enqueue(rootEntity); + PostUpdateCommands.SetComponent(childEntity, new TransformRoot {Value = rootEntity}); + PostUpdateCommands.SetComponent(childEntity, new PreviousParentTransform {Value = parentEntity}); + TransformChildHashMap.Add(parentEntity, childEntity); + } + + public void UpdateChangedRootPRS() + { + var chunks = EntityManager.CreateArchetypeChunkArray( + new ComponentType[] + {typeof(Position), typeof(Rotation), typeof(Heading), typeof(Scale), typeof(UniformScale)}, + new ComponentType[] {typeof(TransformRoot)}, // none + Array.Empty(), // all + Allocator.Temp); + + if (chunks.Length == 0) + { + chunks.Dispose(); + return; + } + + var entityType = EntityManager.GetArchetypeChunkEntityType(true); + + for (int chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++) + { + var chunk = chunks[chunkIndex]; + var updated = UpdateChunkLocal(chunk); + if (updated) + { + var entities = chunk.GetNativeArray(entityType); + var count = chunk.Count; + for (int i = 0; i < count; i++) + { + RootChangedQueue.Enqueue(entities[i]); + } + } + } + + chunks.Dispose(); + } + + public void UpdateChangedChildPRS() + { + var chunks = EntityManager.CreateArchetypeChunkArray( + new ComponentType[] + {typeof(Position), typeof(Rotation), typeof(Heading), typeof(Scale), typeof(UniformScale)}, + Array.Empty(), // none + new ComponentType[] {typeof(TransformRoot), typeof(LocalToParentMatrix)}, // all + Allocator.Temp); + + if (chunks.Length == 0) + { + chunks.Dispose(); + return; + } + + var transformRootType = EntityManager.GetArchetypeChunkComponentType(true); + + for (int chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++) + { + var chunk = chunks[chunkIndex]; + var updated = UpdateChunkLocal(chunk); + if (updated) + { + var transformRoots = chunk.GetNativeArray(transformRootType); + var count = chunk.Count; + for (int i = 0; i < count; i++) + { + RootChangedQueue.Enqueue(transformRoots[i].Value); + } + } + } + + chunks.Dispose(); + } + + private bool UpdateChunkLocal(ArchetypeChunk chunk) + where T : struct, IComponentMatrixData, IComponentData + { + var positionType = EntityManager.GetArchetypeChunkComponentType(true); + var rotationType = EntityManager.GetArchetypeChunkComponentType(true); + var headingType = EntityManager.GetArchetypeChunkComponentType(true); + var scaleType = EntityManager.GetArchetypeChunkComponentType(true); + var uniformScaleType = EntityManager.GetArchetypeChunkComponentType(true); + var localToParentType = EntityManager.GetArchetypeChunkComponentType(false); + + var positionChanged = + ChangeVersionUtility.DidChange(chunk.GetComponentVersion(positionType), LastSystemVersion); + var rotationChanged = + ChangeVersionUtility.DidChange(chunk.GetComponentVersion(rotationType), LastSystemVersion); + var headingChanged = + ChangeVersionUtility.DidChange(chunk.GetComponentVersion(headingType), LastSystemVersion); + var scaleChanged = + ChangeVersionUtility.DidChange(chunk.GetComponentVersion(scaleType), LastSystemVersion); + var uniformScaleChanged = + ChangeVersionUtility.DidChange(chunk.GetComponentVersion(uniformScaleType), LastSystemVersion); + var changed = positionChanged || rotationChanged || headingChanged || scaleChanged || + uniformScaleChanged; + + if (!changed) + return false; + + var positions = chunk.GetNativeArray(positionType); + var rotations = chunk.GetNativeArray(rotationType); + var headings = chunk.GetNativeArray(headingType); + var scales = chunk.GetNativeArray(scaleType); + var uniformScales = chunk.GetNativeArray(uniformScaleType); + var localToParents = chunk.GetNativeArray(localToParentType); + + var existPosition = positions.Length > 0; + var existRotation = rotations.Length > 0; + var existHeading = headings.Length > 0; + var existUniformScale = uniformScales.Length > 0; + var existScale = scales.Length > 0; + + var count = chunk.Count; + + if (existPosition && (!existRotation) && (!existHeading) && (!existUniformScale) && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.translate(positions[i].Value); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if ((!existPosition) && existRotation && (!existHeading) && (!existUniformScale) && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.rottrans(rotations[i].Value, new float3()); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if (existPosition && existRotation && (!existHeading) && (!existUniformScale) && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.rottrans(rotations[i].Value, positions[i].Value); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if ((!existPosition) && (!existRotation) && existHeading && (!existUniformScale) && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.lookRotationToMatrix(new float3(), headings[i].Value, math.up()); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if (existPosition && (!existRotation) && existHeading && (!existUniformScale) && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = + math.lookRotationToMatrix(positions[i].Value, headings[i].Value, math.up()); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if ((!existPosition) && (!existRotation) && (!existHeading) && existUniformScale && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.scale(uniformScales[i].Value); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if (existPosition && (!existRotation) && (!existHeading) && existUniformScale && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.mul(math.translate(positions[i].Value), + math.scale(uniformScales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if ((!existPosition) && existRotation && (!existHeading) && existUniformScale && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.mul(math.rottrans(rotations[i].Value, new float3()), + math.scale(uniformScales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if (existPosition && existRotation && (!existHeading) && existUniformScale && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.mul(math.rottrans(rotations[i].Value, positions[i].Value), + math.scale(uniformScales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if ((!existPosition) && (!existRotation) && existHeading && existUniformScale && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = + math.mul(math.lookRotationToMatrix(new float3(), headings[i].Value, math.up()), + math.scale(uniformScales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if (existPosition && (!existRotation) && existHeading && existUniformScale && (!existScale)) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = + math.mul(math.lookRotationToMatrix(positions[i].Value, headings[i].Value, math.up()), + math.scale(uniformScales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if ((!existPosition) && (!existRotation) && (!existHeading) && (!existUniformScale) && existScale) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.scale(scales[i].Value); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if (existPosition && (!existRotation) && (!existHeading) && (!existUniformScale) && existScale) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = + math.mul(math.translate(positions[i].Value), math.scale(scales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if ((!existPosition) && existRotation && (!existHeading) && (!existUniformScale) && existScale) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.mul(math.rottrans(rotations[i].Value, new float3()), + math.scale(scales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if (existPosition && existRotation && (!existHeading) && (!existUniformScale) && existScale) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = math.mul(math.rottrans(rotations[i].Value, positions[i].Value), + math.scale(scales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if ((!existPosition) && (!existRotation) && existHeading && (!existUniformScale) && existScale) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = + math.mul(math.lookRotationToMatrix(new float3(), headings[i].Value, math.up()), + math.scale(scales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else if (existPosition && (!existRotation) && existHeading && (!existUniformScale) && existScale) + { + for (int i = 0; i < count; i++) + { + float4x4 localToParent = + math.mul(math.lookRotationToMatrix(positions[i].Value, headings[i].Value, math.up()), + math.scale(scales[i].Value)); + localToParents[i] = new T {Value = localToParent}; + } + } + + else + { + throw new System.InvalidOperationException("Invalid combination of transform components"); + } + + return true; + } + + private void UpdateHierarchy(Entity entity, float4x4 parentToWorld) + { + var localToParent = LocalToParentMatrices[entity].Value; + var localToWorld = math.mul(parentToWorld, localToParent); + + LocalToWorldMatrices[entity] = new LocalToWorldMatrix + { + Value = localToWorld + }; + + NativeMultiHashMapIterator it; + Entity child; + + if (!TransformChildHashMap.TryGetFirstValue(entity, out child, out it)) + { + return; + } + + do + { + UpdateHierarchy(child, localToWorld); + } while (TransformChildHashMap.TryGetNextValue(out child, ref it)); + } + + private void UpdateRoot(Entity rootEntity) + { + var localToWorld = LocalToWorldMatrices[rootEntity].Value; + + NativeMultiHashMapIterator it; + Entity child; + + if (!TransformChildHashMap.TryGetFirstValue(rootEntity, out child, out it)) + { + return; + } + + do + { + UpdateHierarchy(child, localToWorld); + } while (TransformChildHashMap.TryGetNextValue(out child, ref it)); + } + + public void UpdateRoots() + { + var changedLength = RootChangedQueue.Count; + if (changedLength == 0) + return; + + LocalToWorldMatrices = EntityManager.GetComponentDataFromEntity(false); + LocalToParentMatrices = EntityManager.GetComponentDataFromEntity(false); + + var rootDirty = new NativeHashMap(changedLength, Allocator.Temp); + var rootChanged = new NativeArray(changedLength, Allocator.Temp); + var rootDirtyCount = 0; + + // Remove duplicate roots + for (int i = 0; i < changedLength; i++) + { + var rootEntity = RootChangedQueue.Dequeue(); + int dirtyIndex; + if (!rootDirty.TryGetValue(rootEntity, out dirtyIndex)) + { + rootChanged[rootDirtyCount] = rootEntity; + rootDirty.TryAdd(rootEntity, rootDirtyCount); + rootDirtyCount++; + } + } + + for (int i = 0; i < rootDirtyCount; i++) + { + var rootEntity = rootChanged[i]; + UpdateRoot(rootEntity); + } + + rootChanged.Dispose(); + rootDirty.Dispose(); + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + // #todo When delete ParentTransform + // #todo When add new ParentTransform, recalc local space + + var positionVersion = EntityManager.GetComponentOrderVersion(); + var rotationVersion = EntityManager.GetComponentOrderVersion(); + var headingVersion = EntityManager.GetComponentOrderVersion(); + var scaleVersion = EntityManager.GetComponentOrderVersion(); + var uniformScaleVersion = EntityManager.GetComponentOrderVersion(); + var parentTransformVersion = EntityManager.GetComponentOrderVersion(); + + var positionChange = positionVersion != LastPositionVersion; + var rotationChange = rotationVersion != LastRotationVersion; + var headingChange = headingVersion != LastHeadingVersion; + var scaleChange = scaleVersion != LastScaleVersion; + var uniformScaleChange = uniformScaleVersion != LastUniformScaleVersion; + var parentTransformChange = parentTransformVersion != LastParentTransformVersion; + var possibleChange = positionChange || rotationChange || headingChange || scaleChange || uniformScaleChange || parentTransformChange; + + if (!possibleChange) + return inputDeps; + + LastPositionVersion = positionVersion; + LastRotationVersion = rotationVersion; + LastHeadingVersion = headingVersion; + LastScaleVersion = scaleVersion; + LastUniformScaleVersion = uniformScaleVersion; + LastParentTransformVersion = parentTransformVersion; + PostUpdateCommands = new EntityCommandBuffer(Allocator.Persistent); + + Debug.Log(string.Format("Transform Patch: {0}", GlobalSystemVersion)); + + RootChangedQueue.Clear(); + + // Stage-0 + // - ParentTransform changed + // - Hash(parent->children) + // - Set TransformRoot + // - UpdateQueue(root) + UpdateAddedParentTransforms(); + UpdateChangedParentTransforms(); + + // Stage-1 + // - root, PRS changed. + UpdateChangedRootPRS(); + + // Stage-2 + // - child, PRS changed. + // - UpdateQueue(child->root) + UpdateChangedChildPRS(); + + // Stage-3 + // - UpdateRoot: LocalToWorldMatrix + // - UpdateSubHierarchy: LocalToWorldMatrix + UpdateRoots(); + + // Complete + PostUpdateCommands.Playback(EntityManager); + PostUpdateCommands.Dispose(); + LastSystemVersion = GlobalSystemVersion; + + return inputDeps; + } + + protected override void OnDestroyManager() + { + TransformChildHashMap.Dispose(); + RootChangedQueue.Dispose(); + PostUpdateCommands.Dispose(); + } + } + + [DisableAutoCreation] + public class TestTransformSetup : ComponentSystem + { + public NativeArray AllEntities; + public int UpdateStep; + + protected override void OnDestroyManager() + { + AllEntities.Dispose(); + } + + protected override void OnUpdate() + { + Debug.Log(string.Format("Update {0}", UpdateStep)); + switch (UpdateStep) + { + case 0: + Update0(); + break; + case 1: + Update1(); + break; + case 2: + Update2(); + break; + } + } + + public void UpdateCase(int step) + { + UpdateStep = step; + Update(); + } + + void Update0() + { + int count = 4; + + AllEntities = new NativeArray(count, Allocator.Persistent); + + AllEntities[0] = EntityManager.CreateEntity( + typeof(LocalToWorldMatrix), + typeof(Position), + typeof(Heading)); + var parentEntity = AllEntities[0]; + + for (int i = 1; i < count; i++) + { + AllEntities[i] = EntityManager.CreateEntity( + typeof(ParentTransform), + typeof(LocalToWorldMatrix), + typeof(LocalToParentMatrix), + typeof(Position), + typeof(Heading)); + EntityManager.SetComponentData(AllEntities[i], new ParentTransform {Value = parentEntity}); + parentEntity = AllEntities[i]; + } + } + + void Update1() + { + var count = AllEntities.Length; + + for (int i = 1; i < count; i++) + { + EntityManager.SetComponentData(AllEntities[i], new ParentTransform {Value = AllEntities[0]}); + } + } + + void Update2() + { + var count = AllEntities.Length; + + for (int i = 1; i < count; i++) + { + EntityManager.RemoveComponents(AllEntities[i]); + /* + float theta = 2 * Mathf.PI * ((float) i / count); + float x = math.cos(theta); + float z = math.sin(theta); + EntityManager.SetComponentData(AllEntities[i], new Heading {Value = new float3(x, 0.0f, z)}); + */ + } + } + } + + // Capture reparenting changes to DAG + [Test] + public void TRA_CatchChangesToParentTransform() + { + var testTransformSetup = World.CreateManager(); + var transformPatch = World.CreateManager(); + + testTransformSetup.UpdateCase(0); + transformPatch.Update(); + testTransformSetup.UpdateCase(1); + transformPatch.Update(); + + var rootEntity = testTransformSetup.AllEntities[0]; + var entityCount = testTransformSetup.AllEntities.Length; + { + + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + Array.Empty(), // none + new ComponentType[] {typeof(ParentTransform)}, // all + Allocator.Temp); + var transformParentType = m_Manager.GetArchetypeChunkComponentType(true); + + var rootChildCount = 0; + for (int chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++) + { + var chunk = chunks[chunkIndex]; + var parentCount = chunk.Count; + + var chunkParentTransforms = chunk.GetNativeArray(transformParentType); + for (int i = 0; i < parentCount; i++) + { + if (chunkParentTransforms[i].Value == rootEntity) + { + rootChildCount++; + } + } + } + + chunks.Dispose(); + Assert.AreEqual(entityCount - 1, rootChildCount); + } + + testTransformSetup.UpdateCase(2); + { + var chunks = m_Manager.CreateArchetypeChunkArray( + Array.Empty(), // any + new ComponentType[] {typeof(ParentTransform)}, // none + new ComponentType[] {typeof(PreviousParentTransform)}, // all + Allocator.Temp); + + Assert.AreEqual(entityCount-1,chunks.EntityCount); + + chunks.Dispose(); + } + + } + } +} diff --git a/Unity.Entities.Tests/TransformUpdateTests.cs.meta b/Unity.Entities.Tests/TransformUpdateTests.cs.meta new file mode 100644 index 00000000..ab9db93d --- /dev/null +++ b/Unity.Entities.Tests/TransformUpdateTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 51f14137b915b48c8a082d7f2aff290f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/TypeManagerTests.cs b/Unity.Entities.Tests/TypeManagerTests.cs new file mode 100644 index 00000000..1d050fa7 --- /dev/null +++ b/Unity.Entities.Tests/TypeManagerTests.cs @@ -0,0 +1,63 @@ +using NUnit.Framework; +using Unity.Entities; + +namespace Unity.Entities.Tests +{ + public class TypeManagerTests : ECSTestsFixture + { + struct TestType1 : IComponentData + { +#pragma warning disable 0169 // "never used" warning + int empty; +#pragma warning restore 0169 + } + struct TestType2 : IComponentData + { +#pragma warning disable 0169 // "never used" warning + int empty; +#pragma warning restore 0169 + } + [Test] + public void CreateArchetypes() + { + var archetype1 = m_Manager.CreateArchetype(ComponentType.Create(), ComponentType.Create()); + var archetype1Same = m_Manager.CreateArchetype(ComponentType.Create(), ComponentType.Create()); + Assert.AreEqual(archetype1, archetype1Same); + + var archetype2 = m_Manager.CreateArchetype(ComponentType.Create()); + var archetype2Same = m_Manager.CreateArchetype(ComponentType.Create()); + Assert.AreEqual(archetype2Same, archetype2Same); + + Assert.AreNotEqual(archetype1, archetype2); + } + + [Test] + public void TestTypeManager() + { + var entity = ComponentType.Create(); + var testData = ComponentType.Create(); + + Assert.AreEqual(entity, ComponentType.Create()); + Assert.AreEqual(entity, new ComponentType(typeof(Entity))); + Assert.AreEqual(testData, ComponentType.Create()); + Assert.AreEqual(testData, new ComponentType(typeof(EcsTestData))); + Assert.AreNotEqual(ComponentType.Create(), ComponentType.Create()); + Assert.AreNotEqual(entity, ComponentType.ReadOnly()); + + Assert.AreEqual(typeof(Entity), entity.GetManagedType()); + } + + struct NonBlittableComponentData : IComponentData + { +#pragma warning disable 0169 // "never used" warning + string empty; +#pragma warning restore 0169 + } + + [Test] + public void NonBlittableComponentDataThrows() + { + Assert.Throws(() => { ComponentType.Create(); }); + } + } +} \ No newline at end of file diff --git a/Unity.Entities.Tests/TypeManagerTests.cs.meta b/Unity.Entities.Tests/TypeManagerTests.cs.meta new file mode 100644 index 00000000..ddb526f8 --- /dev/null +++ b/Unity.Entities.Tests/TypeManagerTests.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 37d4757dc25ba4f37a12806f013b227a +timeCreated: 1505216351 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/Types.meta b/Unity.Entities.Tests/Types.meta new file mode 100644 index 00000000..ffa3d1f3 --- /dev/null +++ b/Unity.Entities.Tests/Types.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f521dcfb85f9f4e479fac091b507204e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/Types/ComponentTypeTests.cs b/Unity.Entities.Tests/Types/ComponentTypeTests.cs new file mode 100644 index 00000000..79433617 --- /dev/null +++ b/Unity.Entities.Tests/Types/ComponentTypeTests.cs @@ -0,0 +1,96 @@ +using NUnit.Framework; + +namespace Unity.Entities.Tests.Types +{ + [TestFixture] + public class ComponentTypeTests : ECSTestsFixture + { + [Test] + public void EqualityOperator_WhenEqual_ReturnsTrue() + { + var t1 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadOnly); + var t2 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadOnly); + + var result = t1 == t2; + + Assert.IsTrue(result); + } + + [Test] + public void EqualityOperator_WhenDifferentType_ReturnsFalse() + { + var t1 = new ComponentType(typeof(EmptySystem), ComponentType.AccessMode.ReadOnly); + var t2 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadOnly); + + var result = t1 == t2; + + Assert.IsFalse(result); + } + + [Test] + public void EqualityOperator_WhenDifferentLength_ReturnsFalse() + { + var t1 = ComponentType.FixedArray(typeof(Entity), 1); + var t2 = ComponentType.FixedArray(typeof(Entity), 2); + + var result = t1 == t2; + + Assert.IsFalse(result); + } + + [Test] + public void EqualityOperator_WhenDifferentAccessMode_ReturnsFalse() + { + var t1 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadWrite); + var t2 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadOnly); + + var result = t1 == t2; + + Assert.IsFalse(result); + } + + [Test] + public void InequalityOperator_WhenEqual_ReturnsFalse() + { + var t1 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadOnly); + var t2 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadOnly); + + var result = t1 != t2; + + Assert.IsFalse(result); + } + + [Test] + public void InequalityOperator_WhenDifferentType_ReturnsTrue() + { + var t1 = new ComponentType(typeof(EmptySystem), ComponentType.AccessMode.ReadOnly); + var t2 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadOnly); + + var result = t1 != t2; + + Assert.IsTrue(result); + } + + [Test] + public void InequalityOperator_WhenDifferentLength_ReturnsTrue() + { + var t1 = ComponentType.FixedArray(typeof(Entity), 1); + var t2 = ComponentType.FixedArray(typeof(Entity), 2); + + var result = t1 != t2; + + Assert.IsTrue(result); + } + + [Test] + public void InequalityOperator_WhenDifferentAccessMode_ReturnsTrue() + { + var t1 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadWrite); + var t2 = new ComponentType(typeof(Entity), ComponentType.AccessMode.ReadOnly); + + var result = t1 != t2; + + Assert.IsTrue(result); + } + } +} diff --git a/Unity.Entities.Tests/Types/ComponentTypeTests.cs.meta b/Unity.Entities.Tests/Types/ComponentTypeTests.cs.meta new file mode 100644 index 00000000..30e4e6b9 --- /dev/null +++ b/Unity.Entities.Tests/Types/ComponentTypeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c20ff75da6adc434b92c0ea6156ef0cc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/Unity.Entities.Tests.asmdef b/Unity.Entities.Tests/Unity.Entities.Tests.asmdef new file mode 100644 index 00000000..d6bfca52 --- /dev/null +++ b/Unity.Entities.Tests/Unity.Entities.Tests.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Unity.Entities.Tests", + "references": [ + "Unity.Entities", + "Unity.Collections", + "Unity.Jobs", + "Unity.Burst", + "Unity.Mathematics" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true +} \ No newline at end of file diff --git a/Unity.Entities.Tests/Unity.Entities.Tests.asmdef.meta b/Unity.Entities.Tests/Unity.Entities.Tests.asmdef.meta new file mode 100644 index 00000000..7544a655 --- /dev/null +++ b/Unity.Entities.Tests/Unity.Entities.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8e2f79f713f5a47dcb69903037d4bc0e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/UpdateOrderOptimizerTests.cs b/Unity.Entities.Tests/UpdateOrderOptimizerTests.cs new file mode 100644 index 00000000..e3531124 --- /dev/null +++ b/Unity.Entities.Tests/UpdateOrderOptimizerTests.cs @@ -0,0 +1,150 @@ +using NUnit.Framework; +using System.Collections.Generic; +using Unity.Entities; +using UnityEngine.TestTools; +using UnityEngine.Experimental.LowLevel; +using UnityEngine; + +namespace Unity.Entities.Tests +{ + public class UpdateOrderOptimizerTests : ECSTestsFixture + { + PlayerLoopSystem m_fakePlayerLoop; + public UpdateOrderOptimizerTests() + { + m_fakePlayerLoop.subSystemList = new PlayerLoopSystem[3]; + m_fakePlayerLoop.subSystemList[0].type = typeof(UnityEngine.Experimental.PlayerLoop.Initialization); + m_fakePlayerLoop.subSystemList[0].subSystemList = new PlayerLoopSystem[0]; + m_fakePlayerLoop.subSystemList[1].type = typeof(UnityEngine.Experimental.PlayerLoop.Update); + m_fakePlayerLoop.subSystemList[1].subSystemList = new PlayerLoopSystem[2]; + m_fakePlayerLoop.subSystemList[1].subSystemList[0].type = typeof(UnityEngine.Experimental.PlayerLoop.Update.ScriptRunBehaviourUpdate); + m_fakePlayerLoop.subSystemList[1].subSystemList[1].type = typeof(UnityEngine.Experimental.PlayerLoop.Update.ScriptRunDelayedDynamicFrameRate); + m_fakePlayerLoop.subSystemList[2].type = typeof(UnityEngine.Experimental.PlayerLoop.PostLateUpdate); + m_fakePlayerLoop.subSystemList[2].subSystemList = new PlayerLoopSystem[0]; + } + + [UpdateInGroup(typeof(RecursiveGroup3))] + class RecursiveGroup1 + {} + [UpdateInGroup(typeof(RecursiveGroup1))] + class RecursiveGroup2 + {} + [UpdateInGroup(typeof(RecursiveGroup2))] + class RecursiveGroup3 + {} + + [UpdateInGroup(typeof(RecursiveGroup3))] + [DisableAutoCreation] + class RecursiveSystem : ComponentSystem + { + protected override void OnUpdate() + { + } + } + + [UpdateAfter(typeof(SimpleCircularSystem3))] + [DisableAutoCreation] + class SimpleCircularSystem1 : ComponentSystem + { + protected override void OnUpdate() + { + } + } + [UpdateAfter(typeof(SimpleCircularSystem1))] + [DisableAutoCreation] + class SimpleCircularSystem2 : ComponentSystem + { + protected override void OnUpdate() + { + } + } + [UpdateAfter(typeof(SimpleCircularSystem2))] + [DisableAutoCreation] + class SimpleCircularSystem3 : ComponentSystem + { + protected override void OnUpdate() + { + } + } + [UpdateAfter(typeof(UnityEngine.Experimental.PlayerLoop.Update))] + [UpdateAfter(typeof(UnityEngine.Experimental.PlayerLoop.Initialization))] + [DisableAutoCreation] + class SimpleOverconstrainedSystem : ComponentSystem + { + protected override void OnUpdate() + { + } + } + [UpdateAfter(typeof(UnityEngine.Experimental.PlayerLoop.Update))] + [DisableAutoCreation] + class OverconstrainedSystem1 : ComponentSystem + { + protected override void OnUpdate() + { + } + } + [UpdateAfter(typeof(OverconstrainedSystem1))] + [UpdateBefore(typeof(OverconstrainedSystem3))] + [DisableAutoCreation] + class OverconstrainedSystem2 : ComponentSystem + { + protected override void OnUpdate() + { + } + } + [UpdateAfter(typeof(UnityEngine.Experimental.PlayerLoop.Initialization))] + [DisableAutoCreation] + class OverconstrainedSystem3 : ComponentSystem + { + protected override void OnUpdate() + { + } + } + + [Test] + public void RecursiveGroupIsError() + { + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex("Found circular chain in update groups involving:")); + + var systems = new HashSet(); + systems.Add(new RecursiveSystem()); + ScriptBehaviourUpdateOrder.InsertManagersInPlayerLoop(systems, m_fakePlayerLoop); + } + [Test] + public void CircularDependencyIsError() + { + // The error is triggered for each system in a chain, not for each chain - so there will be three errors + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex("is in a chain of circular dependencies")); + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex("is in a chain of circular dependencies")); + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex("is in a chain of circular dependencies")); + + var systems = new HashSet(); + systems.Add(new SimpleCircularSystem1()); + systems.Add(new SimpleCircularSystem2()); + systems.Add(new SimpleCircularSystem3()); + ScriptBehaviourUpdateOrder.InsertManagersInPlayerLoop(systems, m_fakePlayerLoop); + } + [Test] + public void OverConstrainedEngineIsError() + { + // The error is triggered for each system in a chain, not for each chain - so there will be three errors + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex("is over constrained with engine containts")); + + var systems = new HashSet(); + systems.Add(new SimpleOverconstrainedSystem()); + ScriptBehaviourUpdateOrder.InsertManagersInPlayerLoop(systems, m_fakePlayerLoop); + } + [Test] + public void OverConstrainedEngineAndSystemIsError() + { + // The error is triggered for each system in a chain, not for each chain - so there will be three errors + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex("is over constrained with engine and system containts")); + + var systems = new HashSet(); + systems.Add(new OverconstrainedSystem1()); + systems.Add(new OverconstrainedSystem2()); + systems.Add(new OverconstrainedSystem3()); + ScriptBehaviourUpdateOrder.InsertManagersInPlayerLoop(systems, m_fakePlayerLoop); + } + } +} diff --git a/Unity.Entities.Tests/UpdateOrderOptimizerTests.cs.meta b/Unity.Entities.Tests/UpdateOrderOptimizerTests.cs.meta new file mode 100644 index 00000000..3d76569c --- /dev/null +++ b/Unity.Entities.Tests/UpdateOrderOptimizerTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e7c9f38b986aa245985652133fb1998 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/WorldDebuggingToolsTests.cs b/Unity.Entities.Tests/WorldDebuggingToolsTests.cs new file mode 100644 index 00000000..8a62c8cf --- /dev/null +++ b/Unity.Entities.Tests/WorldDebuggingToolsTests.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Entities.Tests; + +namespace Unity.Entities.Tests +{ + public class WorldDebuggingToolsTests : ECSTestsFixture + { + + class RegularSystem : ComponentSystem + { + struct Entities + { + public int Length; + public ComponentDataArray tests; + } + +#pragma warning disable 0169 // "never used" warning + [Inject] private Entities entities; +#pragma warning restore 0169 + + protected override void OnUpdate() + { + throw new NotImplementedException(); + } + } + + class SubtractiveSystem : ComponentSystem + { + struct Entities + { + public int Length; + public ComponentDataArray tests; + public SubtractiveComponent noTest2; + } + +#pragma warning disable 0169 // "never used" warning + [Inject] private Entities entities; +#pragma warning restore 0169 + + protected override void OnUpdate() + { + throw new NotImplementedException(); + } + } + + [Test] + public void SystemInclusionList_MatchesComponents() + { + var system = World.Active.GetOrCreateManager(); + + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var matchList = new List>>(); + + WorldDebuggingTools.MatchEntityInComponentGroups(World.Active, entity, matchList); + + Assert.AreEqual(1, matchList.Count); + Assert.AreEqual(system, matchList[0].Item1); + Assert.AreEqual(system.ComponentGroups[0], matchList[0].Item2[0]); + } + + [Test] + public void SystemInclusionList_IgnoresSubtractedComponents() + { + var system = World.Active.GetOrCreateManager(); + + var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2)); + + var matchList = new List>>(); + + WorldDebuggingTools.MatchEntityInComponentGroups(World.Active, entity, matchList); + + Assert.AreEqual(0, matchList.Count); + } + + } +} diff --git a/Unity.Entities.Tests/WorldDebuggingToolsTests.cs.meta b/Unity.Entities.Tests/WorldDebuggingToolsTests.cs.meta new file mode 100644 index 00000000..b81bb84d --- /dev/null +++ b/Unity.Entities.Tests/WorldDebuggingToolsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 952b1ccc934e44a5393df3e3d441fed3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.Tests/WorldTests.cs b/Unity.Entities.Tests/WorldTests.cs new file mode 100644 index 00000000..dc246759 --- /dev/null +++ b/Unity.Entities.Tests/WorldTests.cs @@ -0,0 +1,74 @@ +using NUnit.Framework; + +namespace Unity.Entities.Tests +{ + public class WorldTests + { + World m_PreviousWorld; + + [SetUp] + public virtual void Setup() + { + m_PreviousWorld = World.Active; + } + + [TearDown] + public virtual void TearDown() + { + World.Active = m_PreviousWorld; + } + + + [Test] + public void ActiveWorldResets() + { + int count = World.AllWorlds.Count; + var worldA = new World("WorldA"); + var worldB = new World("WorldB"); + + World.Active = worldB; + + Assert.AreEqual(worldB, World.Active); + Assert.AreEqual(count + 2, World.AllWorlds.Count); + Assert.AreEqual(worldA, World.AllWorlds[World.AllWorlds.Count-2]); + Assert.AreEqual(worldB, World.AllWorlds[World.AllWorlds.Count-1]); + + worldB.Dispose(); + + Assert.IsFalse(worldB.IsCreated); + Assert.IsTrue(worldA.IsCreated); + Assert.AreEqual(null, World.Active); + + worldA.Dispose(); + + Assert.AreEqual(count, World.AllWorlds.Count); + } + + private class TestManager : ComponentSystem + { + protected override void OnUpdate() {} + } + + [Test] + public void WorldVersionIsConsistent() + { + var world = new World("WorldX"); + + Assert.AreEqual(0, world.Version); + + var version = world.Version; + world.GetOrCreateManager(); + Assert.AreNotEqual(version, world.Version); + + version = world.Version; + var manager = world.GetOrCreateManager(); + Assert.AreEqual(version, world.Version); + + version = world.Version; + world.DestroyManager(manager); + Assert.AreNotEqual(version, world.Version); + + world.Dispose(); + } + } +} diff --git a/Unity.Entities.Tests/WorldTests.cs.meta b/Unity.Entities.Tests/WorldTests.cs.meta new file mode 100644 index 00000000..dce467e3 --- /dev/null +++ b/Unity.Entities.Tests/WorldTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8febd451e4d08497e94511e1d729ba1f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities.meta b/Unity.Entities.meta new file mode 100644 index 00000000..85044ef8 --- /dev/null +++ b/Unity.Entities.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 2cbb1448436b043dd864cf585740d0b4 +folderAsset: yes +timeCreated: 1502098479 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/AlwaysUpdateSystemAttribute.cs b/Unity.Entities/AlwaysUpdateSystemAttribute.cs new file mode 100644 index 00000000..badce029 --- /dev/null +++ b/Unity.Entities/AlwaysUpdateSystemAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Unity.Entities +{ + [AttributeUsage(AttributeTargets.Class)] + public class AlwaysUpdateSystemAttribute : Attribute + { + } +} diff --git a/Unity.Entities/AlwaysUpdateSystemAttribute.cs.meta b/Unity.Entities/AlwaysUpdateSystemAttribute.cs.meta new file mode 100644 index 00000000..29b47899 --- /dev/null +++ b/Unity.Entities/AlwaysUpdateSystemAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0734736b79d7ee64cacb7c47173adfa9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ArchetypeManager.cs b/Unity.Entities/ArchetypeManager.cs new file mode 100644 index 00000000..2c35a5ea --- /dev/null +++ b/Unity.Entities/ArchetypeManager.cs @@ -0,0 +1,757 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Unity.Assertions; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal struct ComponentTypeInArchetype + { + public readonly int TypeIndex; + public readonly int FixedArrayLength; + + public bool IsFixedArray => FixedArrayLength != -1; + public int FixedArrayLengthMultiplier => FixedArrayLength != -1 ? FixedArrayLength : 1; + + public ComponentTypeInArchetype(ComponentType type) + { + TypeIndex = type.TypeIndex; + FixedArrayLength = type.FixedArrayLength; + } + + public static bool operator ==(ComponentTypeInArchetype lhs, ComponentTypeInArchetype rhs) + { + return lhs.TypeIndex == rhs.TypeIndex && lhs.FixedArrayLength == rhs.FixedArrayLength; + } + + public static bool operator !=(ComponentTypeInArchetype lhs, ComponentTypeInArchetype rhs) + { + return lhs.TypeIndex != rhs.TypeIndex || lhs.FixedArrayLength != rhs.FixedArrayLength; + } + + public static bool operator <(ComponentTypeInArchetype lhs, ComponentTypeInArchetype rhs) + { + return lhs.TypeIndex != rhs.TypeIndex + ? lhs.TypeIndex < rhs.TypeIndex + : lhs.FixedArrayLength < rhs.FixedArrayLength; + } + + public static bool operator >(ComponentTypeInArchetype lhs, ComponentTypeInArchetype rhs) + { + return lhs.TypeIndex != rhs.TypeIndex + ? lhs.TypeIndex > rhs.TypeIndex + : lhs.FixedArrayLength > rhs.FixedArrayLength; + } + + public static unsafe bool CompareArray(ComponentTypeInArchetype* type1, int typeCount1, + ComponentTypeInArchetype* type2, int typeCount2) + { + if (typeCount1 != typeCount2) + return false; + for (var i = 0; i < typeCount1; ++i) + if (type1[i] != type2[i]) + return false; + return true; + } + + public ComponentType ToComponentType() + { + ComponentType type; + type.FixedArrayLength = FixedArrayLength; + type.TypeIndex = TypeIndex; + type.AccessModeType = ComponentType.AccessMode.ReadWrite; + return type; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public override string ToString() + { + return ToComponentType().ToString(); + } +#endif + public override bool Equals(object obj) + { + if (obj is ComponentTypeInArchetype) return (ComponentTypeInArchetype) obj == this; + + return false; + } + + public override int GetHashCode() + { + return (TypeIndex * 5819) ^ FixedArrayLength; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct Chunk + { + // NOTE: Order of the UnsafeLinkedListNode is required to be this in order + // to allow for casting & grabbing Chunk* from nodes... + public UnsafeLinkedListNode ChunkListNode; // 16 | 8 + public UnsafeLinkedListNode ChunkListWithEmptySlotsNode; // 32 | 16 + + public Archetype* Archetype; // 40 | 20 + public int* SharedComponentValueArray; // 48 | 24 + + // This is meant as read-only. + // ArchetypeManager.SetChunkCount should be used to change the count. + public int Count; // 52 | 28 + public int Capacity; // 56 | 32 + + public int ManagedArrayIndex; // 60 | 36 + + public int Padding0; // 64 | 40 + public uint* ChangeVersion; // 72 | 44 + public void* Padding2; // 80 | 48 + + + // Component data buffer + public fixed byte Buffer[4]; + + + public const int kChunkSize = 16 * 1024; + public const int kMaximumEntitiesPerChunk = kChunkSize / 8; + + public static int GetChunkBufferSize(int numComponents, int numSharedComponents) + { + var bufferSize = kChunkSize - + (sizeof(Chunk) - 4 + numSharedComponents * sizeof(int) + numComponents * sizeof(uint)); + return bufferSize; + } + + public static int GetSharedComponentOffset(int numSharedComponents) + { + return kChunkSize - numSharedComponents * sizeof(int); + } + + public static int GetChangedComponentOffset(int numComponents, int numSharedComponents) + { + return GetSharedComponentOffset(numSharedComponents) - numComponents * sizeof(uint); + } + + public bool MatchesFilter(MatchingArchetypes* match, ref ComponentGroupFilter filter) + { + if (filter.Type == FilterType.SharedComponent) + { + var sharedComponentsInChunk = SharedComponentValueArray; + var filteredCount = filter.Shared.Count; + + fixed (int* indexInComponentGroupPtr = filter.Shared.IndexInComponentGroup, sharedComponentIndexPtr = + filter.Shared.SharedComponentIndex) + { + for (var i = 0; i < filteredCount; ++i) + { + var indexInComponentGroup = indexInComponentGroupPtr[i]; + var sharedComponentIndex = sharedComponentIndexPtr[i]; + var componentIndexInArcheType = match->TypeIndexInArchetypeArray[indexInComponentGroup]; + var componentIndexInChunk = match->Archetype->SharedComponentOffset[componentIndexInArcheType]; + if (sharedComponentsInChunk[componentIndexInChunk] != sharedComponentIndex) + return false; + } + } + + return true; + } + + if (filter.Type == FilterType.Changed) + { + var changedCount = filter.Changed.Count; + + var requiredVersion = filter.RequiredChangeVersion; + fixed (int* indexInComponentGroupPtr = filter.Changed.IndexInComponentGroup) + { + for (var i = 0; i < changedCount; ++i) + { + var indexInArchetype = match->TypeIndexInArchetypeArray[indexInComponentGroupPtr[i]]; + + var changeVersion = ChangeVersion[indexInArchetype]; + if (ChangeVersionUtility.DidChange(changeVersion, requiredVersion)) + return true; + } + } + + return false; + } + + return true; + } + + public int GetSharedComponentIndex(MatchingArchetypes* match, int indexInComponentGroup) + { + var sharedComponentsInChunk = SharedComponentValueArray; + + var componentIndexInArcheType = match->TypeIndexInArchetypeArray[indexInComponentGroup]; + var componentIndexInChunk = match->Archetype->SharedComponentOffset[componentIndexInArcheType]; + return sharedComponentsInChunk[componentIndexInChunk]; + } + } + + internal unsafe struct Archetype + { + public UnsafeLinkedListNode ChunkList; + public UnsafeLinkedListNode ChunkListWithEmptySlots; + + public int EntityCount; + public int ChunkCapacity; + public int ChunkCount; + + public ComponentTypeInArchetype* Types; + public int TypesCount; + + // Index matches archetype types + public int* Offsets; + public int* SizeOfs; + + public int* ManagedArrayOffset; + public int NumManagedArrays; + + public int* SharedComponentOffset; + public int NumSharedComponents; + + public Archetype* PrevArchetype; + } + + internal unsafe class ArchetypeManager : IDisposable + { + private readonly UnsafeLinkedListNode* m_EmptyChunkPool; + + private readonly SharedComponentDataManager m_SharedComponentManager; + private ChunkAllocator m_ArchetypeChunkAllocator; + + internal Archetype* m_LastArchetype; + private ManagedArrayStorage[] m_ManagedArrays = new ManagedArrayStorage[1]; + private NativeMultiHashMap m_TypeLookup; + + public ArchetypeManager(SharedComponentDataManager sharedComponentManager) + { + m_SharedComponentManager = sharedComponentManager; + m_TypeLookup = new NativeMultiHashMap(256, Allocator.Persistent); + + m_EmptyChunkPool = (UnsafeLinkedListNode*) m_ArchetypeChunkAllocator.Allocate(sizeof(UnsafeLinkedListNode), + UnsafeUtility.AlignOf()); + UnsafeLinkedListNode.InitializeList(m_EmptyChunkPool); + +#if UNITY_ASSERTIONS + // Buffer should be 16 byte aligned to ensure component data layout itself can gurantee being aligned + var offset = UnsafeUtility.GetFieldOffset(typeof(Chunk).GetField("Buffer")); + Assert.IsTrue(offset % 16 == 0, "Chunk buffer must be 16 byte aligned"); +#endif + } + + public void Dispose() + { + // Free all allocated chunks for all allocated archetypes + while (m_LastArchetype != null) + { + while (!m_LastArchetype->ChunkList.IsEmpty) + { + var chunk = m_LastArchetype->ChunkList.Begin; + chunk->Remove(); + UnsafeUtility.Free(chunk, Allocator.Persistent); + } + + m_LastArchetype = m_LastArchetype->PrevArchetype; + } + + // And all pooled chunks + while (!m_EmptyChunkPool->IsEmpty) + { + var chunk = m_EmptyChunkPool->Begin; + chunk->Remove(); + UnsafeUtility.Free(chunk, Allocator.Persistent); + } + + m_ManagedArrays = null; + m_TypeLookup.Dispose(); + m_ArchetypeChunkAllocator.Dispose(); + } + + private void DeallocateManagedArrayStorage(int index) + { + Assert.IsTrue(m_ManagedArrays[index].ManagedArray != null); + m_ManagedArrays[index].ManagedArray = null; + } + + private int AllocateManagedArrayStorage(int length) + { + for (var i = 0; i < m_ManagedArrays.Length; i++) + if (m_ManagedArrays[i].ManagedArray == null) + { + m_ManagedArrays[i].ManagedArray = new object[length]; + return i; + } + + var oldLength = m_ManagedArrays.Length; + Array.Resize(ref m_ManagedArrays, m_ManagedArrays.Length * 2); + + m_ManagedArrays[oldLength].ManagedArray = new object[length]; + + return oldLength; + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + public static void AssertArchetypeComponents(ComponentTypeInArchetype* types, int count) + { + if (count < 1) + throw new ArgumentException($"Invalid component count"); + if (types[0].TypeIndex == 0) + throw new ArgumentException($"Component type may not be null"); + if (types[0].TypeIndex != TypeManager.GetTypeIndex()) + throw new ArgumentException($"The Entity ID must always be the first component"); + + for (var i = 1; i < count; i++) + { + if (!TypeManager.IsValidComponentTypeForArchetype(types[i].TypeIndex, types[i].IsFixedArray)) + throw new ArgumentException($"{types[i]} is not a valid component type."); + if (types[i - 1].TypeIndex == types[i].TypeIndex) + throw new ArgumentException( + $"It is not allowed to have two components of the same type on the same entity. ({types[i - 1]} and {types[i]})"); + } + } + + public Archetype* GetExistingArchetype(ComponentTypeInArchetype* types, int count) + { + IntPtr typePtr; + NativeMultiHashMapIterator it; + + if (!m_TypeLookup.TryGetFirstValue(GetHash(types, count), out typePtr, out it)) + return null; + + do + { + var type = (Archetype*) typePtr; + if (ComponentTypeInArchetype.CompareArray(type->Types, type->TypesCount, types, count)) + return type; + } while (m_TypeLookup.TryGetNextValue(out typePtr, ref it)); + + return null; + } + + private static uint GetHash(ComponentTypeInArchetype* types, int count) + { + var hash = HashUtility.Fletcher32((ushort*) types, + count * sizeof(ComponentTypeInArchetype) / sizeof(ushort)); + return hash; + } + + public Archetype* GetOrCreateArchetype(ComponentTypeInArchetype* types, int count, + EntityGroupManager groupManager) + { + var type = GetExistingArchetype(types, count); + if (type != null) + return type; + + AssertArchetypeComponents(types, count); + + // This is a new archetype, allocate it and add it to the hash map + type = (Archetype*) m_ArchetypeChunkAllocator.Allocate(sizeof(Archetype), 8); + type->TypesCount = count; + type->Types = + (ComponentTypeInArchetype*) m_ArchetypeChunkAllocator.Construct( + sizeof(ComponentTypeInArchetype) * count, 4, types); + type->EntityCount = 0; + type->ChunkCount = 0; + + type->NumSharedComponents = 0; + type->SharedComponentOffset = null; + + for (var i = 0; i < count; ++i) + if (TypeManager.GetComponentType(types[i].TypeIndex).Category == + TypeManager.TypeCategory.ISharedComponentData) + ++type->NumSharedComponents; + + var chunkDataSize = Chunk.GetChunkBufferSize(type->TypesCount, type->NumSharedComponents); + + // FIXME: proper alignment + type->Offsets = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4); + type->SizeOfs = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4); + + var bytesPerInstance = 0; + + for (var i = 0; i < count; ++i) + { + var cType = TypeManager.GetComponentType(types[i].TypeIndex); + var sizeOf = cType.SizeInChunk * types[i].FixedArrayLengthMultiplier; + type->SizeOfs[i] = sizeOf; + + bytesPerInstance += sizeOf; + } + + type->ChunkCapacity = chunkDataSize / bytesPerInstance; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (bytesPerInstance > chunkDataSize) + throw new ArgumentException( + $"Entity archetype component data is too large. The maximum component data is {chunkDataSize} but the component data is {bytesPerInstance}"); + + Assert.IsTrue(Chunk.kMaximumEntitiesPerChunk >= type->ChunkCapacity); +#endif + + var usedBytes = 0; + for (var i = 0; i < count; ++i) + { + var sizeOf = type->SizeOfs[i]; + + type->Offsets[i] = usedBytes; + + usedBytes += sizeOf * type->ChunkCapacity; + } + + type->NumManagedArrays = 0; + type->ManagedArrayOffset = null; + + for (var i = 0; i < count; ++i) + if (TypeManager.GetComponentType(types[i].TypeIndex).Category == TypeManager.TypeCategory.Class) + ++type->NumManagedArrays; + + if (type->NumManagedArrays > 0) + { + type->ManagedArrayOffset = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4); + var mi = 0; + for (var i = 0; i < count; ++i) + { + var cType = TypeManager.GetComponentType(types[i].TypeIndex); + if (cType.Category == TypeManager.TypeCategory.Class) + type->ManagedArrayOffset[i] = mi++; + else + type->ManagedArrayOffset[i] = -1; + } + } + + if (type->NumSharedComponents > 0) + { + type->SharedComponentOffset = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4); + var mi = 0; + for (var i = 0; i < count; ++i) + { + var cType = TypeManager.GetComponentType(types[i].TypeIndex); + if (cType.Category == TypeManager.TypeCategory.ISharedComponentData) + type->SharedComponentOffset[i] = mi++; + else + type->SharedComponentOffset[i] = -1; + } + } + + // Update the list of all created archetypes + type->PrevArchetype = m_LastArchetype; + m_LastArchetype = type; + + UnsafeLinkedListNode.InitializeList(&type->ChunkList); + UnsafeLinkedListNode.InitializeList(&type->ChunkListWithEmptySlots); + + m_TypeLookup.Add(GetHash(types, count), (IntPtr) type); + + groupManager.OnArchetypeAdded(type); + + return type; + } + + public static Chunk* GetChunkFromEmptySlotNode(UnsafeLinkedListNode* node) + { + return (Chunk*) (node - 1); + } + + public Chunk* AllocateChunk(Archetype* archetype, int* sharedComponentDataIndices) + { + var buffer = (byte*) UnsafeUtility.Malloc(Chunk.kChunkSize, 64, Allocator.Persistent); + var chunk = (Chunk*) buffer; + ConstructChunk(archetype, chunk, sharedComponentDataIndices); + return chunk; + } + + public static void CopySharedComponentDataIndexArray(int* dest, int* src, int count) + { + if (src == null) + for (var i = 0; i < count; ++i) + dest[i] = 0; + else + for (var i = 0; i < count; ++i) + dest[i] = src[i]; + } + + public void ConstructChunk(Archetype* archetype, Chunk* chunk, int* sharedComponentDataIndices) + { + chunk->Archetype = archetype; + + chunk->Count = 0; + chunk->Capacity = archetype->ChunkCapacity; + chunk->ChunkListNode = new UnsafeLinkedListNode(); + chunk->ChunkListWithEmptySlotsNode = new UnsafeLinkedListNode(); + chunk->SharedComponentValueArray = + (int*) ((byte*) chunk + Chunk.GetSharedComponentOffset(archetype->NumSharedComponents)); + chunk->ChangeVersion = (uint*) ((byte*) chunk + + Chunk.GetChangedComponentOffset(archetype->TypesCount, + archetype->NumSharedComponents)); + + archetype->ChunkList.Add(&chunk->ChunkListNode); + archetype->ChunkCount += 1; + archetype->ChunkListWithEmptySlots.Add(&chunk->ChunkListWithEmptySlotsNode); + + Assert.IsTrue(!archetype->ChunkList.IsEmpty); + Assert.IsTrue(!archetype->ChunkListWithEmptySlots.IsEmpty); + + Assert.IsTrue(chunk == (Chunk*) archetype->ChunkList.Back); + Assert.IsTrue(chunk == GetChunkFromEmptySlotNode(archetype->ChunkListWithEmptySlots.Back)); + + if (archetype->NumManagedArrays > 0) + chunk->ManagedArrayIndex = AllocateManagedArrayStorage(archetype->NumManagedArrays * chunk->Capacity); + else + chunk->ManagedArrayIndex = -1; + + for (var i = 0; i < archetype->TypesCount; i++) + chunk->ChangeVersion[i] = 0; + + if (archetype->NumSharedComponents <= 0) + return; + + var sharedComponentValueArray = chunk->SharedComponentValueArray; + CopySharedComponentDataIndexArray(sharedComponentValueArray, sharedComponentDataIndices, + chunk->Archetype->NumSharedComponents); + + if (sharedComponentDataIndices == null) + return; + + for (var i = 0; i < archetype->NumSharedComponents; ++i) + m_SharedComponentManager.AddReference(sharedComponentValueArray[i]); + } + + private static bool ChunkHasSharedComponents(Chunk* chunk, int* sharedComponentDataIndices) + { + var sharedComponentValueArray = chunk->SharedComponentValueArray; + var numSharedComponents = chunk->Archetype->NumSharedComponents; + if (sharedComponentDataIndices == null) + { + for (var i = 0; i < numSharedComponents; ++i) + if (sharedComponentValueArray[i] != 0) + return false; + } + else + { + for (var i = 0; i < numSharedComponents; ++i) + if (sharedComponentValueArray[i] != sharedComponentDataIndices[i]) + return false; + } + return true; + } + + public Chunk* GetChunkWithEmptySlots(Archetype* archetype, int* sharedComponentDataIndices) + { + // Try existing archetype chunks + if (!archetype->ChunkListWithEmptySlots.IsEmpty) + { + if (archetype->NumSharedComponents == 0) + { + var chunk = GetChunkFromEmptySlotNode(archetype->ChunkListWithEmptySlots.Begin); + Assert.AreNotEqual(chunk->Count, chunk->Capacity); + return chunk; + } + + var end = archetype->ChunkListWithEmptySlots.End; + for (var it = archetype->ChunkListWithEmptySlots.Begin; it != end; it = it->Next) + { + var chunk = GetChunkFromEmptySlotNode(it); + Assert.AreNotEqual(chunk->Count, chunk->Capacity); + if (ChunkHasSharedComponents(chunk, sharedComponentDataIndices)) return chunk; + } + } + + // Try empty chunk pool + if (m_EmptyChunkPool->IsEmpty) + return AllocateChunk(archetype, sharedComponentDataIndices); + + var pooledChunk = (Chunk*) m_EmptyChunkPool->Begin; + pooledChunk->ChunkListNode.Remove(); + + ConstructChunk(archetype, pooledChunk, sharedComponentDataIndices); + return pooledChunk; + + // Allocate new chunk + } + + public int AllocateIntoChunk(Chunk* chunk) + { + int outIndex; + var res = AllocateIntoChunk(chunk, 1, out outIndex); + Assert.AreEqual(1, res); + return outIndex; + } + + public int AllocateIntoChunk(Chunk* chunk, int count, out int outIndex) + { + var allocatedCount = Math.Min(chunk->Capacity - chunk->Count, count); + outIndex = chunk->Count; + SetChunkCount(chunk, chunk->Count + allocatedCount); + chunk->Archetype->EntityCount += allocatedCount; + return allocatedCount; + } + + public void SetChunkCount(Chunk* chunk, int newCount) + { + Assert.AreNotEqual(newCount, chunk->Count); + + var capacity = chunk->Capacity; + + // Chunk released to empty chunk pool + if (newCount == 0) + { + // Remove references to shared components + if (chunk->Archetype->NumSharedComponents > 0) + { + var sharedComponentValueArray = chunk->SharedComponentValueArray; + + for (var i = 0; i < chunk->Archetype->NumSharedComponents; ++i) + m_SharedComponentManager.RemoveReference(sharedComponentValueArray[i]); + } + + if (chunk->ManagedArrayIndex != -1) + { + DeallocateManagedArrayStorage(chunk->ManagedArrayIndex); + chunk->ManagedArrayIndex = -1; + } + + chunk->Archetype = null; + chunk->ChunkListNode.Remove(); + chunk->ChunkListWithEmptySlotsNode.Remove(); + + m_EmptyChunkPool->Add(&chunk->ChunkListNode); + } + // Chunk is now full + else if (newCount == capacity) + { + chunk->ChunkListWithEmptySlotsNode.Remove(); + } + // Chunk is no longer full + else if (chunk->Count == capacity) + { + Assert.IsTrue(newCount < chunk->Count); + + chunk->Archetype->ChunkListWithEmptySlots.Add(&chunk->ChunkListWithEmptySlotsNode); + } + + chunk->Count = newCount; + } + + public object GetManagedObject(Chunk* chunk, ComponentType type, int index) + { + var typeOfs = ChunkDataUtility.GetIndexInTypeArray(chunk->Archetype, type.TypeIndex); + if (typeOfs < 0 || chunk->Archetype->ManagedArrayOffset[typeOfs] < 0) + throw new InvalidOperationException("Trying to get managed object for non existing component"); + return GetManagedObject(chunk, typeOfs, index); + } + + internal object GetManagedObject(Chunk* chunk, int type, int index) + { + var managedStart = chunk->Archetype->ManagedArrayOffset[type] * chunk->Capacity; + return m_ManagedArrays[chunk->ManagedArrayIndex].ManagedArray[index + managedStart]; + } + + public object[] GetManagedObjectRange(Chunk* chunk, int type, out int rangeStart, out int rangeLength) + { + rangeStart = chunk->Archetype->ManagedArrayOffset[type] * chunk->Capacity; + rangeLength = chunk->Count; + return m_ManagedArrays[chunk->ManagedArrayIndex].ManagedArray; + } + + public void SetManagedObject(Chunk* chunk, int type, int index, object val) + { + var managedStart = chunk->Archetype->ManagedArrayOffset[type] * chunk->Capacity; + m_ManagedArrays[chunk->ManagedArrayIndex].ManagedArray[index + managedStart] = val; + } + + public void SetManagedObject(Chunk* chunk, ComponentType type, int index, object val) + { + var typeOfs = ChunkDataUtility.GetIndexInTypeArray(chunk->Archetype, type.TypeIndex); + if (typeOfs < 0 || chunk->Archetype->ManagedArrayOffset[typeOfs] < 0) + throw new InvalidOperationException("Trying to set managed object for non existing component"); + SetManagedObject(chunk, typeOfs, index, val); + } + + public static void MoveChunks(ArchetypeManager srcArchetypeManager, EntityDataManager* srcEntityDataManager, + SharedComponentDataManager srcSharedComponents, ArchetypeManager dstArchetypeManager, + EntityGroupManager dstGroupManager, SharedComponentDataManager dstSharedComponentDataManager, + EntityDataManager* dstEntityDataManager, SharedComponentDataManager dstSharedComponents) + { + var entitiesArray = new NativeArray(Chunk.kMaximumEntitiesPerChunk, Allocator.Temp); + var entitiesPtr = (Entity*) entitiesArray.GetUnsafePtr(); + + var srcArchetype = srcArchetypeManager.m_LastArchetype; + while (srcArchetype != null) + { + if (srcArchetype->EntityCount != 0) + { + if (srcArchetype->NumManagedArrays != 0) + throw new ArgumentException("MoveEntitiesFrom is not supported with managed arrays"); + var dstArchetype = dstArchetypeManager.GetOrCreateArchetype(srcArchetype->Types, + srcArchetype->TypesCount, dstGroupManager); + + for (var c = srcArchetype->ChunkList.Begin; c != srcArchetype->ChunkList.End; c = c->Next) + { + var chunk = (Chunk*) c; + + EntityDataManager.FreeDataEntitiesInChunk(srcEntityDataManager, chunk, chunk->Count); + dstEntityDataManager->AllocateEntities(dstArchetype, chunk, 0, chunk->Count, entitiesPtr); + + chunk->Archetype = dstArchetype; + + if (dstArchetype->NumSharedComponents > 0) + dstSharedComponents.MoveSharedComponents(srcSharedComponents, + chunk->SharedComponentValueArray, dstArchetype->NumSharedComponents); + } + + //@TODO: Patch Entity references in IComponentData... + + UnsafeLinkedListNode.InsertListBefore(dstArchetype->ChunkList.End, &srcArchetype->ChunkList); + UnsafeLinkedListNode.InsertListBefore(dstArchetype->ChunkListWithEmptySlots.End, + &srcArchetype->ChunkListWithEmptySlots); + + dstArchetype->EntityCount += srcArchetype->EntityCount; + dstArchetype->ChunkCount += srcArchetype->ChunkCount; + srcArchetype->EntityCount = 0; + srcArchetype->ChunkCount = 0; + } + + srcArchetype = srcArchetype->PrevArchetype; + } + + entitiesArray.Dispose(); + } + + public int CheckInternalConsistency() + { + var archetype = m_LastArchetype; + var totalCount = 0; + while (archetype != null) + { + var countInArchetype = 0; + for (var c = archetype->ChunkList.Begin; c != archetype->ChunkList.End; c = c->Next) + { + var chunk = (Chunk*) c; + Assert.IsTrue(chunk->Archetype == archetype); + Assert.IsTrue(chunk->Capacity >= chunk->Count); + Assert.AreEqual(chunk->ChunkListWithEmptySlotsNode.IsInList, chunk->Capacity != chunk->Count); + + countInArchetype += chunk->Count; + } + + Assert.AreEqual(countInArchetype, archetype->EntityCount); + + totalCount += countInArchetype; + archetype = archetype->PrevArchetype; + } + + return totalCount; + } + + internal SharedComponentDataManager GetSharedComponentDataManager() + { + return m_SharedComponentManager; + } + + private struct ManagedArrayStorage + { + public object[] ManagedArray; + } + } +} diff --git a/Unity.Entities/ArchetypeManager.cs.meta b/Unity.Entities/ArchetypeManager.cs.meta new file mode 100644 index 00000000..da0b981b --- /dev/null +++ b/Unity.Entities/ArchetypeManager.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 9ce40df16b76747adab69978859250d1 +timeCreated: 1506252434 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/AssemblyInfo.cs b/Unity.Entities/AssemblyInfo.cs new file mode 100644 index 00000000..cf35502e --- /dev/null +++ b/Unity.Entities/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Unity.Entities.Editor")] +[assembly: InternalsVisibleTo("Unity.Entities.Editor.Tests")] +[assembly: InternalsVisibleTo("Unity.Entities.Hybrid")] diff --git a/Unity.Entities/AssemblyInfo.cs.meta b/Unity.Entities/AssemblyInfo.cs.meta new file mode 100644 index 00000000..b15c90e7 --- /dev/null +++ b/Unity.Entities/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17533d104276f4858a05586eb615a90a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ChangeVersionUtility.cs b/Unity.Entities/ChangeVersionUtility.cs new file mode 100644 index 00000000..20cecf70 --- /dev/null +++ b/Unity.Entities/ChangeVersionUtility.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Unity.Entities +{ + static class ChangeVersionUtility + { + public static bool DidChange(uint changeVersion, uint requiredVersion) + { + // initial state data never triggers a change + if (changeVersion == 0) + return false; + // When a system runs for the first time, everything is considered changed. + if (requiredVersion == 0) + return true; + // Supporting wrap around for version numbers, change must be bigger than last system run. + // (Never detect change of something the system itself changed) + return (int)(changeVersion - requiredVersion) > 0; + } + + public static void IncrementGlobalSystemVersion(ref uint globalSystemVersion) + { + globalSystemVersion++; + // Handle wrap around, 0 is reserved for systems that have never run.. + if (globalSystemVersion == 0) + globalSystemVersion++; + } + + // 0 is reserved for systems that have never run + public const int InitialGlobalSystemVersion = 1; + + } +} diff --git a/Unity.Entities/ChangeVersionUtility.cs.meta b/Unity.Entities/ChangeVersionUtility.cs.meta new file mode 100644 index 00000000..e4c7bae9 --- /dev/null +++ b/Unity.Entities/ChangeVersionUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe4fdc30fdadb443286427029cf39b87 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ChunkAllocator.cs b/Unity.Entities/ChunkAllocator.cs new file mode 100644 index 00000000..5a0c70d0 --- /dev/null +++ b/Unity.Entities/ChunkAllocator.cs @@ -0,0 +1,56 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal unsafe struct ChunkAllocator : IDisposable + { + private byte* m_FirstChunk; + private byte* m_LastChunk; + private int m_LastChunkUsedSize; + private const int ms_ChunkSize = 64 * 1024; + private const int ms_ChunkAlignment = 64; + + public void Dispose() + { + while (m_FirstChunk != null) + { + var nextChunk = ((byte**) m_FirstChunk)[0]; + UnsafeUtility.Free(m_FirstChunk, Allocator.Persistent); + m_FirstChunk = nextChunk; + } + + m_LastChunk = null; + } + + public byte* Allocate(int size, int alignment) + { + var alignedChunkSize = (m_LastChunkUsedSize + alignment - 1) & ~(alignment - 1); + if (m_LastChunk == null || size > ms_ChunkSize - alignedChunkSize) + { + // Allocate new chunk + var newChunk = (byte*) UnsafeUtility.Malloc(ms_ChunkSize, ms_ChunkAlignment, Allocator.Persistent); + ((byte**) newChunk)[0] = null; + if (m_LastChunk != null) + ((byte**) m_LastChunk)[0] = newChunk; + else + m_FirstChunk = newChunk; + m_LastChunk = newChunk; + m_LastChunkUsedSize = sizeof(byte*); + alignedChunkSize = (m_LastChunkUsedSize + alignment - 1) & ~(alignment - 1); + } + + var ptr = m_LastChunk + alignedChunkSize; + m_LastChunkUsedSize = alignedChunkSize + size; + return ptr; + } + + public byte* Construct(int size, int alignment, void* src) + { + var res = Allocate(size, alignment); + UnsafeUtility.MemCpy(res, src, size); + return res; + } + } +} diff --git a/Unity.Entities/ChunkAllocator.cs.meta b/Unity.Entities/ChunkAllocator.cs.meta new file mode 100644 index 00000000..a14d7808 --- /dev/null +++ b/Unity.Entities/ChunkAllocator.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 311219e85bf1d4ab1a4def2044b4c49b +timeCreated: 1504682893 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ChunkDataUtility.cs b/Unity.Entities/ChunkDataUtility.cs new file mode 100644 index 00000000..b6202082 --- /dev/null +++ b/Unity.Entities/ChunkDataUtility.cs @@ -0,0 +1,286 @@ +using System; +using Unity.Assertions; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal static unsafe class ChunkDataUtility + { + public static int GetIndexInTypeArray(Archetype* archetype, int typeIndex) + { + var types = archetype->Types; + var typeCount = archetype->TypesCount; + for (var i = 0; i != typeCount; i++) + if (typeIndex == types[i].TypeIndex) + return i; + + return -1; + } + + public static void GetIndexInTypeArray(Archetype* archetype, int typeIndex, ref int typeLookupCache) + { + var types = archetype->Types; + var typeCount = archetype->TypesCount; + + if (typeLookupCache < typeCount && types[typeLookupCache].TypeIndex == typeIndex) + return; + + for (var i = 0; i != typeCount; i++) + { + if (typeIndex != types[i].TypeIndex) + continue; + + typeLookupCache = i; + return; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("Shouldn't happen"); +#endif + } + + public static void GetComponentDataWithTypeAndFixedArrayLength(Chunk* chunk, int index, int typeIndex, + out byte* outPtr, out int outArrayLength) + { + var archetype = chunk->Archetype; + var indexInTypeArray = GetIndexInTypeArray(archetype, typeIndex); + + var offset = archetype->Offsets[indexInTypeArray]; + var sizeOf = archetype->SizeOfs[indexInTypeArray]; + + outPtr = chunk->Buffer + (offset + sizeOf * index); + outArrayLength = archetype->Types[indexInTypeArray].FixedArrayLength; + } + + public static byte* GetComponentDataWithTypeRO(Chunk* chunk, int index, int typeIndex, ref int typeLookupCache) + { + var archetype = chunk->Archetype; + GetIndexInTypeArray(archetype, typeIndex, ref typeLookupCache); + var indexInTypeArray = typeLookupCache; + + var offset = archetype->Offsets[indexInTypeArray]; + var sizeOf = archetype->SizeOfs[indexInTypeArray]; + + return chunk->Buffer + (offset + sizeOf * index); + } + + public static byte* GetComponentDataWithTypeRW(Chunk* chunk, int index, int typeIndex, uint globalSystemVersion, + ref int typeLookupCache) + { + var archetype = chunk->Archetype; + GetIndexInTypeArray(archetype, typeIndex, ref typeLookupCache); + var indexInTypeArray = typeLookupCache; + + var offset = archetype->Offsets[indexInTypeArray]; + var sizeOf = archetype->SizeOfs[indexInTypeArray]; + + chunk->ChangeVersion[indexInTypeArray] = globalSystemVersion; + + return chunk->Buffer + (offset + sizeOf * index); + } + + public static byte* GetComponentDataWithTypeRO(Chunk* chunk, int index, int typeIndex) + { + var indexInTypeArray = GetIndexInTypeArray(chunk->Archetype, typeIndex); + + var offset = chunk->Archetype->Offsets[indexInTypeArray]; + var sizeOf = chunk->Archetype->SizeOfs[indexInTypeArray]; + + return chunk->Buffer + (offset + sizeOf * index); + } + + public static byte* GetComponentDataWithTypeRW(Chunk* chunk, int index, int typeIndex, uint globalSystemVersion) + { + var indexInTypeArray = GetIndexInTypeArray(chunk->Archetype, typeIndex); + + var offset = chunk->Archetype->Offsets[indexInTypeArray]; + var sizeOf = chunk->Archetype->SizeOfs[indexInTypeArray]; + + chunk->ChangeVersion[indexInTypeArray] = globalSystemVersion; + + return chunk->Buffer + (offset + sizeOf * index); + } + + public static byte* GetComponentDataRO(Chunk* chunk, int index, int indexInTypeArray) + { + var offset = chunk->Archetype->Offsets[indexInTypeArray]; + var sizeOf = chunk->Archetype->SizeOfs[indexInTypeArray]; + + return chunk->Buffer + (offset + sizeOf * index); + } + + public static byte* GetComponentDataRW(Chunk* chunk, int index, int indexInTypeArray, uint globalSystemVersion) + { + var offset = chunk->Archetype->Offsets[indexInTypeArray]; + var sizeOf = chunk->Archetype->SizeOfs[indexInTypeArray]; + + chunk->ChangeVersion[indexInTypeArray] = globalSystemVersion; + + return chunk->Buffer + (offset + sizeOf * index); + } + + public static void Copy(Chunk* srcChunk, int srcIndex, Chunk* dstChunk, int dstIndex, int count) + { + Assert.IsTrue(srcChunk->Archetype == dstChunk->Archetype); + + var arch = srcChunk->Archetype; + var srcBuffer = srcChunk->Buffer; + var dstBuffer = dstChunk->Buffer; + var offsets = arch->Offsets; + var sizeOfs = arch->SizeOfs; + var typesCount = arch->TypesCount; + + for (var t = 0; t < typesCount; t++) + { + var offset = offsets[t]; + var sizeOf = sizeOfs[t]; + var src = srcBuffer + (offset + sizeOf * srcIndex); + var dst = dstBuffer + (offset + sizeOf * dstIndex); + + dstChunk->ChangeVersion[t] = srcChunk->ChangeVersion[t]; + UnsafeUtility.MemCpy(dst, src, sizeOf * count); + } + } + + public static void ClearComponents(Chunk* dstChunk, int dstIndex, int count) + { + var arch = dstChunk->Archetype; + + var offsets = arch->Offsets; + var sizeOfs = arch->SizeOfs; + var dstBuffer = dstChunk->Buffer; + var typesCount = arch->TypesCount; + + for (var t = 1; t != typesCount; t++) + { + var offset = offsets[t]; + var sizeOf = sizeOfs[t]; + var dst = dstBuffer + (offset + sizeOf * dstIndex); + + UnsafeUtility.MemClear(dst, sizeOf * count); + } + } + + public static void ReplicateComponents(Chunk* srcChunk, int srcIndex, Chunk* dstChunk, int dstBaseIndex, + int count) + { + Assert.IsTrue(srcChunk->Archetype == dstChunk->Archetype); + + var arch = srcChunk->Archetype; + var srcBuffer = srcChunk->Buffer; + var dstBuffer = dstChunk->Buffer; + var offsets = arch->Offsets; + var sizeOfs = arch->SizeOfs; + var typesCount = arch->TypesCount; + // type[0] is always Entity, and will be patched up later, so just skip + for (var t = 1; t != typesCount; t++) + { + var offset = offsets[t]; + var sizeOf = sizeOfs[t]; + var src = srcBuffer + (offset + sizeOf * srcIndex); + var dst = dstBuffer + (offset + sizeOf * dstBaseIndex); + + UnsafeUtility.MemCpyReplicate(dst, src, sizeOf, count); + } + } + + public static void Convert(Chunk* srcChunk, int srcIndex, Chunk* dstChunk, int dstIndex) + { + var srcArch = srcChunk->Archetype; + var dstArch = dstChunk->Archetype; + + var srcI = 0; + var dstI = 0; + while (srcI < srcArch->TypesCount && dstI < dstArch->TypesCount) + if (srcArch->Types[srcI] < dstArch->Types[dstI]) + { + ++srcI; + } + else if (srcArch->Types[srcI] > dstArch->Types[dstI]) + { + // Clear components in the destination that aren't copied + var dst = dstChunk->Buffer + dstArch->Offsets[dstI] + dstIndex * dstArch->SizeOfs[dstI]; + UnsafeUtility.MemClear(dst, dstArch->SizeOfs[dstI]); + ++dstI; + } + else + { + var src = srcChunk->Buffer + srcArch->Offsets[srcI] + srcIndex * srcArch->SizeOfs[srcI]; + var dst = dstChunk->Buffer + dstArch->Offsets[dstI] + dstIndex * dstArch->SizeOfs[dstI]; + UnsafeUtility.MemCpy(dst, src, srcArch->SizeOfs[srcI]); + ++srcI; + ++dstI; + } + + // Clear remaining components in the destination that aren't copied + for (; dstI < dstArch->TypesCount; ++dstI) + { + var dst = dstChunk->Buffer + dstArch->Offsets[dstI] + dstIndex * dstArch->SizeOfs[dstI]; + UnsafeUtility.MemClear(dst, dstArch->SizeOfs[dstI]); + } + } + + public static void PoisonUnusedChunkData(Chunk* chunk, byte value) + { + var arch = chunk->Archetype; + var bufferSize = Chunk.GetChunkBufferSize(arch->TypesCount, arch->NumSharedComponents); + var buffer = chunk->Buffer; + var count = chunk->Count; + + for (var i = 0; i < arch->TypesCount - 1; ++i) + { + var startOffset = arch->Offsets[i] + count * arch->SizeOfs[i]; + var endOffset = arch->Offsets[i + 1]; + UnsafeUtilityEx.MemSet(buffer + startOffset, value, endOffset - startOffset); + } + + var lastStartOffset = arch->Offsets[arch->TypesCount - 1] + count * arch->SizeOfs[arch->TypesCount - 1]; + UnsafeUtilityEx.MemSet(buffer + lastStartOffset, value, bufferSize - lastStartOffset); + } + + public static void CopyManagedObjects(ArchetypeManager typeMan, Chunk* srcChunk, int srcStartIndex, + Chunk* dstChunk, int dstStartIndex, int count) + { + var srcArch = srcChunk->Archetype; + var dstArch = dstChunk->Archetype; + + var srcI = 0; + var dstI = 0; + while (srcI < srcArch->TypesCount && dstI < dstArch->TypesCount) + if (srcArch->Types[srcI] < dstArch->Types[dstI]) + { + ++srcI; + } + else if (srcArch->Types[srcI] > dstArch->Types[dstI]) + { + ++dstI; + } + else + { + if (srcArch->ManagedArrayOffset[srcI] >= 0) + for (var i = 0; i < count; ++i) + { + var obj = typeMan.GetManagedObject(srcChunk, srcI, srcStartIndex + i); + typeMan.SetManagedObject(dstChunk, dstI, dstStartIndex + i, obj); + } + + ++srcI; + ++dstI; + } + } + + public static void ClearManagedObjects(ArchetypeManager typeMan, Chunk* chunk, int index, int count) + { + var arch = chunk->Archetype; + + for (var type = 0; type < arch->TypesCount; ++type) + { + if (arch->ManagedArrayOffset[type] < 0) + continue; + + for (var i = 0; i < count; ++i) + typeMan.SetManagedObject(chunk, type, index + i, null); + } + } + } +} diff --git a/Unity.Entities/ChunkDataUtility.cs.meta b/Unity.Entities/ChunkDataUtility.cs.meta new file mode 100644 index 00000000..5cb274b5 --- /dev/null +++ b/Unity.Entities/ChunkDataUtility.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 6cfda8024903748ee80a4daa13bb7c8b +timeCreated: 1504725069 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ComponentJobManager.cs b/Unity.Entities/ComponentJobManager.cs new file mode 100644 index 00000000..aebe533d --- /dev/null +++ b/Unity.Entities/ComponentJobManager.cs @@ -0,0 +1,374 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using UnityEngine.Profiling; + +namespace Unity.Entities +{ + internal unsafe class ComponentJobSafetyManager + { + private const int kMaxReadJobHandles = 17; + private const int kMaxTypes = TypeManager.MaximumTypesCount; + + private readonly JobHandle* m_JobDependencyCombineBuffer; + private readonly int m_JobDependencyCombineBufferCount; + private ComponentSafetyHandle* m_ComponentSafetyHandles; + + private JobHandle m_ExclusiveTransactionDependency; + + private bool m_HasCleanHandles; + + private JobHandle* m_ReadJobFences; + + public ComponentJobSafetyManager() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_TempSafety = AtomicSafetyHandle.Create(); +#endif + + + m_ReadJobFences = (JobHandle*) UnsafeUtility.Malloc(sizeof(JobHandle) * kMaxReadJobHandles * kMaxTypes, 16, + Allocator.Persistent); + UnsafeUtility.MemClear(m_ReadJobFences, sizeof(JobHandle) * kMaxReadJobHandles * kMaxTypes); + + m_ComponentSafetyHandles = + (ComponentSafetyHandle*) UnsafeUtility.Malloc(sizeof(ComponentSafetyHandle) * kMaxTypes, 16, + Allocator.Persistent); + UnsafeUtility.MemClear(m_ComponentSafetyHandles, sizeof(ComponentSafetyHandle) * kMaxTypes); + + m_JobDependencyCombineBufferCount = 4 * 1024; + m_JobDependencyCombineBuffer = (JobHandle*) UnsafeUtility.Malloc( + sizeof(ComponentSafetyHandle) * m_JobDependencyCombineBufferCount, 16, Allocator.Persistent); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (var i = 0; i != kMaxTypes; i++) + { + m_ComponentSafetyHandles[i].SafetyHandle = AtomicSafetyHandle.Create(); + AtomicSafetyHandle.SetAllowSecondaryVersionWriting(m_ComponentSafetyHandles[i].SafetyHandle, false); + } +#endif + + m_HasCleanHandles = true; + } + + public bool IsInTransaction { get; private set; } + + public JobHandle ExclusiveTransactionDependency + { + get { return m_ExclusiveTransactionDependency; } + set + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!IsInTransaction) + throw new InvalidOperationException( + "EntityManager.TransactionDependency can only after EntityManager.BeginExclusiveEntityTransaction has been called."); + + if (!JobHandle.CheckFenceIsDependencyOrDidSyncFence(m_ExclusiveTransactionDependency, value)) + throw new InvalidOperationException( + "EntityManager.TransactionDependency must depend on the Entity Transaction job."); +#endif + m_ExclusiveTransactionDependency = value; + } + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public AtomicSafetyHandle ExclusiveTransactionSafety { get; private set; } +#endif + + //@TODO: Optimize as one function call to in batch bump version on every single handle... + public void CompleteAllJobsAndInvalidateArrays() + { + if (m_HasCleanHandles) + return; + + Profiler.BeginSample("CompleteAllJobsAndInvalidateArrays"); + + var count = TypeManager.GetTypeCount(); + for (var t = 0; t != count; t++) + { + m_ComponentSafetyHandles[t].WriteFence.Complete(); + + var readFencesCount = m_ComponentSafetyHandles[t].NumReadFences; + var readFences = m_ReadJobFences + t * kMaxReadJobHandles; + for (var r = 0; r != readFencesCount; r++) + readFences[r].Complete(); + m_ComponentSafetyHandles[t].NumReadFences = 0; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (var i = 0; i != count; i++) + AtomicSafetyHandle.CheckDeallocateAndThrow(m_ComponentSafetyHandles[i].SafetyHandle); + + for (var i = 0; i != count; i++) + { + AtomicSafetyHandle.Release(m_ComponentSafetyHandles[i].SafetyHandle); + m_ComponentSafetyHandles[i].SafetyHandle = AtomicSafetyHandle.Create(); + AtomicSafetyHandle.SetAllowSecondaryVersionWriting(m_ComponentSafetyHandles[i].SafetyHandle, false); + } +#endif + + m_HasCleanHandles = true; + + Profiler.EndSample(); + } + + public void Dispose() + { + for (var i = 0; i < kMaxTypes; i++) + m_ComponentSafetyHandles[i].WriteFence.Complete(); + + for (var i = 0; i < kMaxTypes * kMaxReadJobHandles; i++) + m_ReadJobFences[i].Complete(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (var i = 0; i < kMaxTypes; i++) + { + var res = AtomicSafetyHandle.EnforceAllBufferJobsHaveCompletedAndRelease(m_ComponentSafetyHandles[i] + .SafetyHandle); + if (res == EnforceJobResult.DidSyncRunningJobs) + Debug.LogError( + "Disposing EntityManager but a job is still running against the ComponentData. It appears the job has not been registered with JobComponentSystem.AddDependency."); + } + + AtomicSafetyHandle.Release(m_TempSafety); + +#endif + + UnsafeUtility.Free(m_JobDependencyCombineBuffer, Allocator.Persistent); + + UnsafeUtility.Free(m_ComponentSafetyHandles, Allocator.Persistent); + m_ComponentSafetyHandles = null; + + UnsafeUtility.Free(m_ReadJobFences, Allocator.Persistent); + m_ReadJobFences = null; + } + + public void CompleteDependencies(int* readerTypes, int readerTypesCount, int* writerTypes, int writerTypesCount) + { + for (var i = 0; i != writerTypesCount; i++) + CompleteReadAndWriteDependency(writerTypes[i]); + + for (var i = 0; i != readerTypesCount; i++) + CompleteWriteDependency(readerTypes[i]); + } + + public bool HasReaderOrWriterDependency(int type, JobHandle dependency) + { + var writer = m_ComponentSafetyHandles[type].WriteFence; + if (JobHandle.CheckFenceIsDependencyOrDidSyncFence(dependency, writer)) + return true; + + var count = m_ComponentSafetyHandles[type].NumReadFences; + for (var r = 0; r < count; r++) + { + var reader = m_ReadJobFences[type * kMaxReadJobHandles + r]; + if (JobHandle.CheckFenceIsDependencyOrDidSyncFence(dependency, reader)) + return true; + } + + return false; + } + + public JobHandle GetDependency(int* readerTypes, int readerTypesCount, int* writerTypes, int writerTypesCount) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (readerTypesCount * kMaxReadJobHandles + writerTypesCount > m_JobDependencyCombineBufferCount) + throw new ArgumentException("Too many readers & writers in GetDependency"); +#endif + + var count = 0; + for (var i = 0; i != readerTypesCount; i++) + m_JobDependencyCombineBuffer[count++] = m_ComponentSafetyHandles[readerTypes[i]].WriteFence; + + for (var i = 0; i != writerTypesCount; i++) + { + var writerType = writerTypes[i]; + + m_JobDependencyCombineBuffer[count++] = m_ComponentSafetyHandles[writerType].WriteFence; + + var numReadFences = m_ComponentSafetyHandles[writerType].NumReadFences; + for (var j = 0; j != numReadFences; j++) + m_JobDependencyCombineBuffer[count++] = m_ReadJobFences[writerType * kMaxReadJobHandles + j]; + } + + return JobHandleUnsafeUtility.CombineDependencies(m_JobDependencyCombineBuffer, + count); + } + + public JobHandle AddDependency(int* readerTypes, int readerTypesCount, int* writerTypes, int writerTypesCount, + JobHandle dependency) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + JobHandle* combinedDependencies = null; + var combinedDependenciesCount = 0; +#endif + + for (var i = 0; i != writerTypesCount; i++) + { + var writer = writerTypes[i]; + m_ComponentSafetyHandles[writer].WriteFence = dependency; + } + + + for (var i = 0; i != readerTypesCount; i++) + { + var reader = readerTypes[i]; + m_ReadJobFences[reader * kMaxReadJobHandles + m_ComponentSafetyHandles[reader].NumReadFences] = + dependency; + m_ComponentSafetyHandles[reader].NumReadFences++; + + if (m_ComponentSafetyHandles[reader].NumReadFences == kMaxReadJobHandles) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var combined = CombineReadDependencies(reader); + if (combinedDependencies == null) + { + JobHandle* temp = stackalloc JobHandle[readerTypesCount]; + combinedDependencies = temp; + } + + combinedDependencies[combinedDependenciesCount++] = combined; +#else + CombineReadDependencies(reader); + #endif + } + } + + if (readerTypesCount != 0 || writerTypesCount != 0) + m_HasCleanHandles = false; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (combinedDependencies != null) + return JobHandleUnsafeUtility.CombineDependencies(combinedDependencies, combinedDependenciesCount); + return dependency; +#else + return dependency; +#endif + } + + public void CompleteWriteDependency(int type) + { + m_ComponentSafetyHandles[type].WriteFence.Complete(); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_ComponentSafetyHandles[type].SafetyHandle); +#endif + } + + public void CompleteReadAndWriteDependency(int type) + { + for (var i = 0; i < m_ComponentSafetyHandles[type].NumReadFences; ++i) + m_ReadJobFences[type * kMaxReadJobHandles + i].Complete(); + m_ComponentSafetyHandles[type].NumReadFences = 0; + + m_ComponentSafetyHandles[type].WriteFence.Complete(); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndThrow(m_ComponentSafetyHandles[type].SafetyHandle); +#endif + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public AtomicSafetyHandle GetSafetyHandle(int type, bool isReadOnly) + { + m_HasCleanHandles = false; + var handle = m_ComponentSafetyHandles[type].SafetyHandle; + if (isReadOnly) + AtomicSafetyHandle.UseSecondaryVersion(ref handle); + + return handle; + } +#endif + + private JobHandle CombineReadDependencies(int type) + { + var combined = JobHandleUnsafeUtility.CombineDependencies( + m_ReadJobFences + type * kMaxReadJobHandles, m_ComponentSafetyHandles[type].NumReadFences); + + m_ReadJobFences[type * kMaxReadJobHandles] = combined; + m_ComponentSafetyHandles[type].NumReadFences = 1; + + return combined; + } + + public void BeginExclusiveTransaction() + { + if (IsInTransaction) + return; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (var i = 0; i != TypeManager.GetTypeCount(); i++) + AtomicSafetyHandle.CheckDeallocateAndThrow(m_ComponentSafetyHandles[i].SafetyHandle); +#endif + + IsInTransaction = true; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + ExclusiveTransactionSafety = AtomicSafetyHandle.Create(); +#endif + m_ExclusiveTransactionDependency = GetAllDependencies(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (var i = 0; i != TypeManager.GetTypeCount(); i++) + AtomicSafetyHandle.Release(m_ComponentSafetyHandles[i].SafetyHandle); +#endif + } + + public void EndExclusiveTransaction() + { + if (!IsInTransaction) + return; + + m_ExclusiveTransactionDependency.Complete(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var res = AtomicSafetyHandle.EnforceAllBufferJobsHaveCompletedAndRelease(ExclusiveTransactionSafety); + if (res != EnforceJobResult.AllJobsAlreadySynced) + //@TODO: Better message + Debug.LogError("ExclusiveEntityTransaction job has not been registered"); +#endif + IsInTransaction = false; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (var i = 0; i != TypeManager.GetTypeCount(); i++) + { + m_ComponentSafetyHandles[i].SafetyHandle = AtomicSafetyHandle.Create(); + AtomicSafetyHandle.SetAllowSecondaryVersionWriting(m_ComponentSafetyHandles[i].SafetyHandle, false); + } +#endif + } + + private JobHandle GetAllDependencies() + { + var jobHandles = + new NativeArray(TypeManager.GetTypeCount() * (kMaxReadJobHandles + 1), Allocator.Temp); + + var count = 0; + for (var i = 0; i != TypeManager.GetTypeCount(); i++) + { + jobHandles[count++] = m_ComponentSafetyHandles[i].WriteFence; + + var numReadFences = m_ComponentSafetyHandles[i].NumReadFences; + for (var j = 0; j != numReadFences; j++) + jobHandles[count++] = m_ReadJobFences[i * kMaxReadJobHandles + j]; + } + + var combined = JobHandle.CombineDependencies(jobHandles); + jobHandles.Dispose(); + + return combined; + } + + private struct ComponentSafetyHandle + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public AtomicSafetyHandle SafetyHandle; +#endif + public JobHandle WriteFence; + public int NumReadFences; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly AtomicSafetyHandle m_TempSafety; +#endif + } +} diff --git a/Unity.Entities/ComponentJobManager.cs.meta b/Unity.Entities/ComponentJobManager.cs.meta new file mode 100644 index 00000000..b4acc3cd --- /dev/null +++ b/Unity.Entities/ComponentJobManager.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 8501f8cf2faad4628b86c6cfa71b8c22 +timeCreated: 1504851159 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ComponentSystem.cs b/Unity.Entities/ComponentSystem.cs new file mode 100644 index 00000000..3f8e18e5 --- /dev/null +++ b/Unity.Entities/ComponentSystem.cs @@ -0,0 +1,556 @@ +using System; +using System.Reflection; +using Unity.Collections; +using Unity.Jobs; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs.LowLevel.Unsafe; + +namespace Unity.Entities +{ + public unsafe abstract class ComponentSystemBase : ScriptBehaviourManager + { + InjectComponentGroupData[] m_InjectedComponentGroups; + InjectFromEntityData m_InjectFromEntityData; + + ComponentGroupArrayStaticCache[] m_CachedComponentGroupArrays; + ComponentGroup[] m_ComponentGroups; + + NativeList m_JobDependencyForReadingManagers; + NativeList m_JobDependencyForWritingManagers; + + internal int* m_JobDependencyForReadingManagersPtr; + internal int m_JobDependencyForReadingManagersLength; + + internal int* m_JobDependencyForWritingManagersPtr; + internal int m_JobDependencyForWritingManagersLength; + + uint m_LastSystemVersion; + + internal ComponentJobSafetyManager m_SafetyManager; + internal EntityManager m_EntityManager; + World m_World; + + bool m_AlwaysUpdateSystem; + internal bool m_PreviouslyEnabled; + + public bool Enabled { get; set; } = true; + public ComponentGroup[] ComponentGroups => m_ComponentGroups; + + public uint GlobalSystemVersion => m_EntityManager.GlobalSystemVersion; + + public bool ShouldRunSystem() + { + if (m_AlwaysUpdateSystem) + return true; + + var length = m_ComponentGroups?.Length ?? 0; + + if (length == 0) + return true; + + // If all the groups are empty, skip it. + // (There’s no way to know what they key value is without other markup) + for (int i = 0;i != length;i++) + { + if (!m_ComponentGroups[i].IsEmptyIgnoreFilter) + return true; + } + + return false; + } + + protected override void OnBeforeCreateManagerInternal(World world, int capacity) + { + m_World = world; + m_EntityManager = world.GetOrCreateManager(); + m_SafetyManager = m_EntityManager.ComponentJobSafetyManager; + m_AlwaysUpdateSystem = GetType().GetCustomAttributes(typeof(AlwaysUpdateSystemAttribute), true).Length != 0; + + m_ComponentGroups = new ComponentGroup[0]; + m_CachedComponentGroupArrays = new ComponentGroupArrayStaticCache[0]; + m_JobDependencyForReadingManagers = new NativeList(10, Allocator.Persistent); + m_JobDependencyForWritingManagers = new NativeList(10, Allocator.Persistent); + + ComponentSystemInjection.Inject(this, world, m_EntityManager, out m_InjectedComponentGroups, out m_InjectFromEntityData); + m_InjectFromEntityData.ExtractJobDependencyTypes(this); + + InjectNestedIJobProcessComponentDataJobs(); + + UpdateInjectedComponentGroups(); + } + + void InjectNestedIJobProcessComponentDataJobs() + { + // Create ComponentGroup for all nested IJobProcessComponentData jobs + foreach (var nestedType in GetType().GetNestedTypes(BindingFlags.FlattenHierarchy | BindingFlags.NonPublic)) + { + var componentTypes = IJobProcessComponentDataUtility.GetComponentTypes(nestedType); + if (componentTypes != null) + GetComponentGroup(componentTypes); + } + } + + protected sealed override void OnAfterDestroyManagerInternal() + { + foreach (var group in m_ComponentGroups) + { + #if ENABLE_UNITY_COLLECTIONS_CHECKS + group.DisallowDisposing = null; + #endif + group.Dispose(); + } + m_ComponentGroups = null; + m_InjectedComponentGroups = null; + m_CachedComponentGroupArrays = null; + + if (m_JobDependencyForReadingManagers.IsCreated) + m_JobDependencyForReadingManagers.Dispose(); + if (m_JobDependencyForWritingManagers.IsCreated) + m_JobDependencyForWritingManagers.Dispose(); + } + + protected override void OnBeforeDestroyManagerInternal() + { + if (m_PreviouslyEnabled) + { + m_PreviouslyEnabled = false; + OnStopRunning(); + } + CompleteDependencyInternal(); + UpdateInjectedComponentGroups(); + } + + protected virtual void OnStartRunning() + { + + } + + protected virtual void OnStopRunning() + { + + } + + protected internal void BeforeUpdateVersioning() + { + m_EntityManager.Entities->IncrementGlobalSystemVersion(); + foreach (var group in m_ComponentGroups) + group.SetFilterChangedRequiredVersion(m_LastSystemVersion); + } + + protected internal void AfterUpdateVersioning() + { + m_LastSystemVersion = EntityManager.Entities->GlobalSystemVersion; + } + + protected EntityManager EntityManager => m_EntityManager; + protected World World => m_World; + + // TODO: this should be made part of UnityEngine? + static void ArrayUtilityAdd(ref T[] array, T item) + { + Array.Resize(ref array, array.Length + 1); + array[array.Length - 1] = item; + } + + internal void AddReaderWriter(ComponentType componentType) + { + if (CalculateReaderWriterDependency.Add(componentType, m_JobDependencyForReadingManagers, m_JobDependencyForWritingManagers)) + { + m_JobDependencyForReadingManagersPtr = (int*)m_JobDependencyForReadingManagers.GetUnsafePtr(); + m_JobDependencyForWritingManagersPtr = (int*)m_JobDependencyForWritingManagers.GetUnsafePtr(); + + m_JobDependencyForReadingManagersLength = m_JobDependencyForReadingManagers.Length; + m_JobDependencyForWritingManagersLength = m_JobDependencyForWritingManagers.Length; + CompleteDependencyInternal(); + } + } + + internal ComponentGroup GetComponentGroupInternal(ComponentType* componentTypes, int count) + { + for (var i = 0; i != m_ComponentGroups.Length; i++) + { + if (m_ComponentGroups[i].CompareComponents(componentTypes, count)) + return m_ComponentGroups[i]; + } + + var group = EntityManager.CreateComponentGroup(componentTypes, count); + group.SetFilterChangedRequiredVersion(m_LastSystemVersion); + #if ENABLE_UNITY_COLLECTIONS_CHECKS + group.DisallowDisposing = "ComponentGroup.Dispose() may not be called on a ComponentGroup created with ComponentSystem.GetComponentGroup. The ComponentGroup will automatically be disposed by the ComponentSystem."; + #endif + + ArrayUtilityAdd(ref m_ComponentGroups, group); + + for (int i = 0;i != count;i++) + AddReaderWriter(componentTypes[i]); + + //@TODO: Shouldn't this sync fence on the newly depent types? + + return group; + } + + internal ComponentGroup GetComponentGroupInternal(ComponentType[] componentTypes) + { + fixed (ComponentType* typesPtr = componentTypes) + { + return GetComponentGroupInternal(typesPtr, componentTypes.Length); + } + } + + + protected ComponentGroup GetComponentGroup(params ComponentType[] componentTypes) + { + fixed (ComponentType* typesPtr = componentTypes) + { + return GetComponentGroupInternal(typesPtr, componentTypes.Length); + } + } + + protected ComponentGroupArray GetEntities() where T : struct + { + for (var i = 0; i != m_CachedComponentGroupArrays.Length; i++) + { + if (m_CachedComponentGroupArrays[i].CachedType == typeof(T)) + return new ComponentGroupArray(m_CachedComponentGroupArrays[i]); + } + + var cache = new ComponentGroupArrayStaticCache(typeof(T), EntityManager, this); + ArrayUtilityAdd(ref m_CachedComponentGroupArrays, cache); + return new ComponentGroupArray(cache); + } + + protected void UpdateInjectedComponentGroups() + { + if (null == m_InjectedComponentGroups) + return; + + ulong gchandle; + var pinnedSystemPtr = (byte*)UnsafeUtility.PinGCObjectAndGetAddress(this, out gchandle); + + try + { + foreach (var group in m_InjectedComponentGroups) + group.UpdateInjection (pinnedSystemPtr); + + m_InjectFromEntityData.UpdateInjection(pinnedSystemPtr, EntityManager); + } + catch + { + UnsafeUtility.ReleaseGCObject(gchandle); + throw; + } + UnsafeUtility.ReleaseGCObject(gchandle); + } + + internal void CompleteDependencyInternal() + { + m_SafetyManager.CompleteDependencies(m_JobDependencyForReadingManagersPtr, m_JobDependencyForReadingManagersLength, m_JobDependencyForWritingManagersPtr, m_JobDependencyForWritingManagersLength); + } + } + + public abstract class ComponentSystem : ComponentSystemBase + { + EntityCommandBuffer m_DeferredEntities; + + public EntityCommandBuffer PostUpdateCommands => m_DeferredEntities; + + unsafe void BeforeOnUpdate() + { + BeforeUpdateVersioning(); + CompleteDependencyInternal(); + UpdateInjectedComponentGroups(); + + m_DeferredEntities = new EntityCommandBuffer(Allocator.TempJob); + } + + void AfterOnUpdate() + { + AfterUpdateVersioning(); + + JobHandle.ScheduleBatchedJobs(); + + m_DeferredEntities.Playback(EntityManager); + m_DeferredEntities.Dispose(); + } + + internal sealed override void InternalUpdate() + { + if (Enabled && ShouldRunSystem()) + { + if (!m_PreviouslyEnabled) + { + m_PreviouslyEnabled = true; + OnStartRunning(); + } + + BeforeOnUpdate(); + + try + { + OnUpdate(); + } + finally + { + AfterOnUpdate(); + } + } + else if (m_PreviouslyEnabled) + { + m_PreviouslyEnabled = false; + OnStopRunning(); + } + } + + protected sealed override void OnBeforeCreateManagerInternal(World world, int capacity) + { + base.OnBeforeCreateManagerInternal(world, capacity); + } + + protected sealed override void OnBeforeDestroyManagerInternal() + { + base.OnBeforeDestroyManagerInternal(); + } + + /// + /// Called once per frame on the main thread. + /// + protected abstract void OnUpdate(); +} + + public abstract class JobComponentSystem : ComponentSystemBase + { + JobHandle m_PreviousFrameDependency; + BarrierSystem[] m_BarrierList; + + unsafe JobHandle BeforeOnUpdate() + { + BeforeUpdateVersioning(); + + UpdateInjectedComponentGroups(); + + // We need to wait on all previous frame dependencies, otherwise it is possible that we create infinitely long dependency chains + // without anyone ever waiting on it + m_PreviousFrameDependency.Complete(); + + return GetDependency(); + } + + unsafe void AfterOnUpdate(JobHandle outputJob, bool throwException) + { + AfterUpdateVersioning(); + + JobHandle.ScheduleBatchedJobs(); + + AddDependencyInternal(outputJob); + + // Notify all injected barrier systems that they will need to sync on any jobs we spawned. + // This is conservative currently - the barriers will sync on too much if we use more than one. + for (int i = 0; i < m_BarrierList.Length; ++i) + { + m_BarrierList[i].AddJobHandleForProducer(outputJob); + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + + if (!JobsUtility.JobDebuggerEnabled) + return; + + // Check that all reading and writing jobs are a dependency of the output job, to + // catch systems that forget to add one of their jobs to the dependency graph. + // + // Note that this check is not strictly needed as we would catch the mistake anyway later, + // but checking it here means we can flag the system that has the mistake, rather than some + // other (innocent) system that is doing things correctly. + + //@TODO: It is not ideal that we call m_SafetyManager.GetDependency, + // it can result in JobHandle.CombineDependencies calls. + // Which seems like debug code might have side-effects + + string dependencyError = null; + for (var index = 0; index < m_JobDependencyForReadingManagersLength && dependencyError == null; index++) + { + var type = m_JobDependencyForReadingManagersPtr[index]; + dependencyError = CheckJobDependencies(type); + } + + for (var index = 0; index < m_JobDependencyForWritingManagersLength && dependencyError == null; index++) + { + var type = m_JobDependencyForWritingManagersPtr[index]; + dependencyError = CheckJobDependencies(type); + } + + if (dependencyError != null) + { + EmergencySyncAllJobs(); + + if (throwException) + throw new System.InvalidOperationException(dependencyError); + } +#endif + } + + internal sealed override void InternalUpdate() + { + if (Enabled && ShouldRunSystem()) + { + if (!m_PreviouslyEnabled) + { + m_PreviouslyEnabled = true; + OnStartRunning(); + } + + var inputJob = BeforeOnUpdate(); + JobHandle outputJob = new JobHandle(); + try + { + outputJob = OnUpdate(inputJob); + } + catch + { + AfterOnUpdate(outputJob, false); + throw; + } + + AfterOnUpdate(outputJob, true); + } + else if (m_PreviouslyEnabled) + { + m_PreviouslyEnabled = false; + OnStopRunning(); + } + } + + protected sealed override void OnBeforeCreateManagerInternal(World world, int capacity) + { + base.OnBeforeCreateManagerInternal(world, capacity); + + m_BarrierList = ComponentSystemInjection.GetAllInjectedManagers(this, world); + } + + protected sealed override void OnBeforeDestroyManagerInternal() + { + base.OnBeforeDestroyManagerInternal(); + m_PreviousFrameDependency.Complete(); + } + + public ComponentDataFromEntity GetComponentDataFromEntity(bool isReadOnly = false) where T : struct, IComponentData + { + AddReaderWriter(isReadOnly ? ComponentType.ReadOnly() : ComponentType.Create()); + return EntityManager.GetComponentDataFromEntity(TypeManager.GetTypeIndex(), isReadOnly); + } + + public FixedArrayFromEntity GetFixedArrayFromEntity(bool isReadOnly = false) where T : struct + { + AddReaderWriter(isReadOnly ? ComponentType.ReadOnly() : ComponentType.Create()); + return EntityManager.GetFixedArrayFromEntity(TypeManager.GetTypeIndex(), isReadOnly); + } + + protected virtual JobHandle OnUpdate(JobHandle inputDeps) + { + return inputDeps; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + unsafe string CheckJobDependencies(int type) + { + var h = m_SafetyManager.GetSafetyHandle(type, true); + + var readerCount = AtomicSafetyHandle.GetReaderArray(h, 0, IntPtr.Zero); + JobHandle* readers = stackalloc JobHandle[readerCount]; + AtomicSafetyHandle.GetReaderArray(h, readerCount, (IntPtr) readers); + + for (var i = 0; i < readerCount; ++i) + { + if (!m_SafetyManager.HasReaderOrWriterDependency(type, readers[i])) + return $"The system {GetType()} reads {TypeManager.GetType(type)} via {AtomicSafetyHandle.GetReaderName(h, i)} but that type was not returned as a job dependency. To ensure correct behavior of other systems, the job or a dependency of it must be returned from the OnUpdate method."; + } + + if (!m_SafetyManager.HasReaderOrWriterDependency(type, AtomicSafetyHandle.GetWriter(h))) + return $"The system {GetType()} writes {TypeManager.GetType(type)} via {AtomicSafetyHandle.GetWriterName(h)} but that was not returned as a job dependency. To ensure correct behavior of other systems, the job or a dependency of it must be returned from the OnUpdate method."; + + return null; + } + + unsafe void EmergencySyncAllJobs() + { + for (int i = 0;i != m_JobDependencyForReadingManagersLength;i++) + { + int type = m_JobDependencyForReadingManagersPtr[i]; + AtomicSafetyHandle.EnforceAllBufferJobsHaveCompleted(m_SafetyManager.GetSafetyHandle(type, true)); + } + + for (int i = 0;i != m_JobDependencyForWritingManagersLength;i++) + { + int type = m_JobDependencyForWritingManagersPtr[i]; + AtomicSafetyHandle.EnforceAllBufferJobsHaveCompleted(m_SafetyManager.GetSafetyHandle(type, true)); + } + } +#endif + + unsafe JobHandle GetDependency () + { + return m_SafetyManager.GetDependency(m_JobDependencyForReadingManagersPtr, m_JobDependencyForReadingManagersLength, m_JobDependencyForWritingManagersPtr, m_JobDependencyForWritingManagersLength); + } + + unsafe void AddDependencyInternal(JobHandle dependency) + { + m_PreviousFrameDependency = m_SafetyManager.AddDependency(m_JobDependencyForReadingManagersPtr, m_JobDependencyForReadingManagersLength, m_JobDependencyForWritingManagersPtr, m_JobDependencyForWritingManagersLength, dependency); + } + } + + public unsafe abstract class BarrierSystem : ComponentSystem + { + private NativeList m_PendingBuffers; + private JobHandle m_ProducerHandle; + + public EntityCommandBuffer CreateCommandBuffer() + { + var cmds = new EntityCommandBuffer(Allocator.TempJob); + + m_PendingBuffers.Add(cmds); + + return cmds; + } + + internal void AddJobHandleForProducer(JobHandle foo) + { + m_ProducerHandle = JobHandle.CombineDependencies(m_ProducerHandle, foo); + } + + protected override void OnCreateManager(int capacity) + { + base.OnCreateManager(capacity); + + m_PendingBuffers = new NativeList(Allocator.Persistent); + } + + protected override void OnDestroyManager() + { + FlushBuffers(false); + + m_PendingBuffers.Dispose(); + + base.OnDestroyManager(); + } + + protected sealed override void OnUpdate() + { + FlushBuffers(true); + + m_PendingBuffers.Clear(); + } + + private void FlushBuffers(bool playBack) + { + m_ProducerHandle.Complete(); + m_ProducerHandle = new JobHandle(); + + for (int i = 0; i < m_PendingBuffers.Length; ++i) + { + if (playBack) + m_PendingBuffers[i].Playback(EntityManager); + m_PendingBuffers[i].Dispose(); + } + } + } +} diff --git a/Unity.Entities/ComponentSystem.cs.meta b/Unity.Entities/ComponentSystem.cs.meta new file mode 100644 index 00000000..a867caeb --- /dev/null +++ b/Unity.Entities/ComponentSystem.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d16037e44a79a44a3b36bc9f23fa96fc +timeCreated: 1492009931 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ComponentSystemPatchAttribute.cs b/Unity.Entities/ComponentSystemPatchAttribute.cs new file mode 100644 index 00000000..f39085ec --- /dev/null +++ b/Unity.Entities/ComponentSystemPatchAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Unity.Entities +{ + [AttributeUsage(AttributeTargets.Class)] + public class ComponentSystemPatchAttribute : Attribute + { + } +} diff --git a/Unity.Entities/ComponentSystemPatchAttribute.cs.meta b/Unity.Entities/ComponentSystemPatchAttribute.cs.meta new file mode 100644 index 00000000..5913d359 --- /dev/null +++ b/Unity.Entities/ComponentSystemPatchAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2723f11a93c2f424ea06efb0bb1914a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/CopyUtility.cs b/Unity.Entities/CopyUtility.cs new file mode 100644 index 00000000..37beda55 --- /dev/null +++ b/Unity.Entities/CopyUtility.cs @@ -0,0 +1,55 @@ +using Unity.Collections; +using Unity.Jobs; + +namespace Unity.Entities +{ + /// + /// Copy ComponentDataArray to NativeArray Job. + /// + /// Component data type stored in ComponentDataArray to be copied to NativeArray + [ComputeJobOptimization] + public struct CopyComponentData : IJobParallelFor + where T : struct, IComponentData + { + [ReadOnly] public ComponentDataArray Source; + public NativeArray Results; + + public void Execute(int index) + { + Results[index] = Source[index]; + } + } + + /// + /// Assign Value to each element of NativeArray + /// + /// Type of element in NativeArray + [ComputeJobOptimization] + public struct MemsetNativeArray : IJobParallelFor + where T : struct + { + public NativeArray Source; + public T Value; + + // #todo Need equivalent of IJobParallelFor that's per-chunk so we can do memset per chunk here. + public void Execute(int index) + { + Source[index] = Value; + } + } + + /// + /// Copy Entities from EntityArray to NativeArray + /// + [ComputeJobOptimization] + public struct CopyEntities : IJobParallelFor + { + [ReadOnly] public EntityArray Source; + public NativeArray Results; + + public void Execute(int index) + { + Results[index] = Source[index]; + } + } +} diff --git a/Unity.Entities/CopyUtility.cs.meta b/Unity.Entities/CopyUtility.cs.meta new file mode 100644 index 00000000..9c267e16 --- /dev/null +++ b/Unity.Entities/CopyUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f1c54a855439444eaf5700708fd197e0 +timeCreated: 1519264689 \ No newline at end of file diff --git a/Unity.Entities/DisallowRefReturnCrossingThisAttribute.cs b/Unity.Entities/DisallowRefReturnCrossingThisAttribute.cs new file mode 100644 index 00000000..5c33d07c --- /dev/null +++ b/Unity.Entities/DisallowRefReturnCrossingThisAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Unity.Entities +{ + /// + /// What is this : Attribute signaling that ref returned values, of a type that has this attribute, cannot intersect + /// with + /// calls to methods that also have this attribute. + /// Motivation(s): ref returns of values that are backed by native memory (unsafe), like IComponentData in ecs chunks, + /// can have the referenced + /// memory invalidated by certain methods. A way is needed to detect these situations a compilation time to prevent + /// accessing invalidated references. + /// Notes: + /// - This attribute is used/feeds a Static Analyzer at compilation time. + /// - Attribute transfers with aggragations: struct A has this attribute, struct B has a field of type A; both A and B + /// are concidered to have the attribute. + /// + [AttributeUsage(AttributeTargets.Struct + | AttributeTargets.Method + | AttributeTargets.Property + | AttributeTargets.Interface)] + public class DisallowRefReturnCrossingThisAttribute : Attribute + { + } +} diff --git a/Unity.Entities/DisallowRefReturnCrossingThisAttribute.cs.meta b/Unity.Entities/DisallowRefReturnCrossingThisAttribute.cs.meta new file mode 100644 index 00000000..7cb23807 --- /dev/null +++ b/Unity.Entities/DisallowRefReturnCrossingThisAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 263c6422322df4970aecdc7736467d42 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/EndFrameBarrier.cs b/Unity.Entities/EndFrameBarrier.cs new file mode 100644 index 00000000..c4055fa7 --- /dev/null +++ b/Unity.Entities/EndFrameBarrier.cs @@ -0,0 +1,11 @@ +using UnityEngine.Experimental.PlayerLoop; +using UnityEngine.Scripting; + +namespace Unity.Entities +{ + [UpdateBefore(typeof(Initialization))] + [Preserve] + public class EndFrameBarrier : BarrierSystem + { + } +} diff --git a/Unity.Entities/EndFrameBarrier.cs.meta b/Unity.Entities/EndFrameBarrier.cs.meta new file mode 100644 index 00000000..5d968465 --- /dev/null +++ b/Unity.Entities/EndFrameBarrier.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1276b8b55e336b4d961c8432fc300dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/EntityCommandBuffer.cs b/Unity.Entities/EntityCommandBuffer.cs new file mode 100644 index 00000000..7bbba65a --- /dev/null +++ b/Unity.Entities/EntityCommandBuffer.cs @@ -0,0 +1,490 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace Unity.Entities +{ + internal unsafe struct EntityCommandBufferData + { + public EntityCommandBuffer.Chunk* m_Tail; + public EntityCommandBuffer.Chunk* m_Head; + public EntityCommandBuffer.EntitySharedComponentCommand* m_CleanupList; + + public int m_MinimumChunkSize; + + public Allocator m_Allocator; + } + + /// + /// A thread-safe command buffer that can buffer commands that affect entities and components for later playback. + /// + /// Command buffers are not created in user code directly, you get them from either a ComponentSystem or a Barrier. + [StructLayout(LayoutKind.Sequential)] + [NativeContainer] + public unsafe struct EntityCommandBuffer + { + /// + /// The minimum chunk size to allocate from the job allocator. + /// + /// We keep this relatively small as we don't want to overload the temp allocator in case people make a ton of command buffers. + /// + /// Organized in memory like a single block with Chunk header followed by Size bytes of data. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct Chunk + { + internal int Used; + internal int Size; + internal Chunk* Next; + internal Chunk* Prev; + + internal int Capacity => Size - Used; + + internal int Bump(int size) + { + var off = Used; + Used += size; + return off; + } + } + + private const int kDefaultMinimumChunkSize = 4 * 1024; + + [NativeDisableUnsafePtrRestriction] private EntityCommandBufferData* m_Data; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly AtomicSafetyHandle m_Safety; +#endif + + /// + /// Allows controlling the size of chunks allocated from the temp job allocator to back the command buffer. + /// + /// Larger sizes are more efficient, but create more waste in the allocator. + public int MinimumChunkSize + { + get => m_Data->m_MinimumChunkSize > 0 ? m_Data->m_MinimumChunkSize : kDefaultMinimumChunkSize; + set => m_Data->m_MinimumChunkSize = Math.Max(0, value); + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BasicCommand + { + public int CommandType; + public int TotalSize; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CreateCommand + { + public BasicCommand Header; + public EntityArchetype Archetype; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct EntityCommand + { + public BasicCommand Header; + public Entity Entity; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct EntityComponentCommand + { + public EntityCommand Header; + public int ComponentTypeIndex; + + public int ComponentSize; + // Data follows if command has an associated component payload + } + + [StructLayout(LayoutKind.Sequential)] + internal struct EntitySharedComponentCommand + { + public EntityCommand Header; + public int ComponentTypeIndex; + public int HashCode; + public GCHandle BoxedObject; + public EntitySharedComponentCommand* Prev; + + internal object GetBoxedObject() + { + if (BoxedObject.IsAllocated) + return BoxedObject.Target; + return null; + } + } + + private byte* Reserve(int size) + { + var data = m_Data; + + if (data->m_Tail == null || data->m_Tail->Capacity < size) + { + var chunkSize = math.max(MinimumChunkSize, size); + + var c = (Chunk*) UnsafeUtility.Malloc(sizeof(Chunk) + chunkSize, 16, data->m_Allocator); + var prev = data->m_Tail; + c->Next = null; + c->Prev = prev; + c->Used = 0; + c->Size = chunkSize; + + if (prev != null) prev->Next = c; + + if (data->m_Head == null) data->m_Head = c; + + data->m_Tail = c; + } + + var offset = data->m_Tail->Bump(size); + var ptr = (byte*) data->m_Tail + sizeof(Chunk) + offset; + return ptr; + } + + private void AddCreateCommand(Command op, EntityArchetype archetype) + { + var data = (CreateCommand*) Reserve(sizeof(CreateCommand)); + + data->Header.CommandType = (int) op; + data->Header.TotalSize = sizeof(CreateCommand); + data->Archetype = archetype; + } + + private void AddEntityCommand(Command op, Entity e) + { + var data = (EntityCommand*) Reserve(sizeof(EntityCommand)); + + data->Header.CommandType = (int) op; + data->Header.TotalSize = sizeof(EntityCommand); + data->Entity = e; + } + + private void AddEntityComponentCommand(Command op, Entity e, T component) where T : struct + { + var typeSize = UnsafeUtility.SizeOf(); + var typeIndex = TypeManager.GetTypeIndex(); + var sizeNeeded = Align(sizeof(EntityComponentCommand) + typeSize, 8); + + var data = (EntityComponentCommand*) Reserve(sizeNeeded); + + data->Header.Header.CommandType = (int) op; + data->Header.Header.TotalSize = sizeNeeded; + data->Header.Entity = e; + data->ComponentTypeIndex = typeIndex; + data->ComponentSize = typeSize; + + UnsafeUtility.CopyStructureToPtr(ref component, (byte*) (data + 1)); + } + + private static int Align(int size, int alignmentPowerOfTwo) + { + return (size + alignmentPowerOfTwo - 1) & ~(alignmentPowerOfTwo - 1); + } + + private void AddEntityComponentTypeCommand(Command op, Entity e, ComponentType t) + { + var sizeNeeded = Align(sizeof(EntityComponentCommand), 8); + + var data = (EntityComponentCommand*) Reserve(sizeNeeded); + + data->Header.Header.CommandType = (int) op; + data->Header.Header.TotalSize = sizeNeeded; + data->Header.Entity = e; + data->ComponentTypeIndex = t.TypeIndex; + } + + private void AddEntitySharedComponentCommand(Command op, Entity e, int hashCode, object boxedObject) + where T : struct + { + var typeIndex = TypeManager.GetTypeIndex(); + var sizeNeeded = Align(sizeof(EntitySharedComponentCommand), 8); + + var data = (EntitySharedComponentCommand*) Reserve(sizeNeeded); + + data->Header.Header.CommandType = (int) op; + data->Header.Header.TotalSize = sizeNeeded; + data->Header.Entity = e; + data->ComponentTypeIndex = typeIndex; + data->HashCode = hashCode; + + if (boxedObject != null) + { + data->BoxedObject = GCHandle.Alloc(boxedObject); + // We need to store all GCHandles on a cleanup list so we can dispose them later, regardless of if playback occurs or not. + data->Prev = m_Data->m_CleanupList; + m_Data->m_CleanupList = data; + } + else + { + data->BoxedObject = new GCHandle(); + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void EnforceSingleThreadOwnership() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); +#endif + } + + /// + /// Creates a new command buffer. Note that this is internal and not exposed to user code. + /// + /// Memory allocator to use for chunks and data + internal EntityCommandBuffer(Allocator label) + { + m_Data = (EntityCommandBufferData*) UnsafeUtility.Malloc(sizeof(EntityCommandBufferData), + UnsafeUtility.AlignOf(), label); + m_Data->m_Allocator = label; + m_Data->m_Tail = null; + m_Data->m_Head = null; + m_Data->m_MinimumChunkSize = kDefaultMinimumChunkSize; + m_Data->m_CleanupList = null; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_Safety = AtomicSafetyHandle.Create(); +#endif + } + + internal void Dispose() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckDeallocateAndThrow(m_Safety); +#endif + if (m_Data != null) + { + var cleanup_list = m_Data->m_CleanupList; + while (cleanup_list != null) + { + cleanup_list->BoxedObject.Free(); + cleanup_list = cleanup_list->Prev; + } + + m_Data->m_CleanupList = null; + + var label = m_Data->m_Allocator; + + while (m_Data->m_Tail != null) + { + var prev = m_Data->m_Tail->Prev; + UnsafeUtility.Free(m_Data->m_Tail, m_Data->m_Allocator); + m_Data->m_Tail = prev; + } + + m_Data->m_Head = null; + UnsafeUtility.Free(m_Data, label); + m_Data = null; + } + } + + public void CreateEntity() + { + EnforceSingleThreadOwnership(); + AddCreateCommand(Command.CreateEntity, new EntityArchetype()); + } + + public void CreateEntity(EntityArchetype archetype) + { + EnforceSingleThreadOwnership(); + AddCreateCommand(Command.CreateEntity, archetype); + } + + public void DestroyEntity(Entity e) + { + EnforceSingleThreadOwnership(); + AddEntityCommand(Command.DestroyEntity, e); + } + + public void AddComponent(Entity e, T component) where T : struct, IComponentData + { + EnforceSingleThreadOwnership(); + AddEntityComponentCommand(Command.AddComponent, e, component); + } + + public void AddComponent(T component) where T : struct, IComponentData + { + AddComponent(Entity.Null, component); + } + + public void SetComponent(T component) where T : struct, IComponentData + { + SetComponent(Entity.Null, component); + } + + public void SetComponent(Entity e, T component) where T : struct, IComponentData + { + EnforceSingleThreadOwnership(); + AddEntityComponentCommand(Command.SetComponent, e, component); + } + + public void RemoveComponent(Entity e) + { + EnforceSingleThreadOwnership(); + AddEntityComponentTypeCommand(Command.RemoveComponent, e, ComponentType.Create()); + } + + public void RemoveSystemStateComponent(Entity e) + { + EnforceSingleThreadOwnership(); + AddEntityComponentTypeCommand(Command.RemoveSystemStateComponent, e, ComponentType.Create()); + } + + private static bool IsDefaultObject(ref T component, out int hashCode) where T : struct, ISharedComponentData + { + var typeIndex = TypeManager.GetTypeIndex(); + var layout = TypeManager.GetComponentType(typeIndex).FastEqualityLayout; + var defaultValue = default(T); + hashCode = FastEquality.GetHashCode(ref component, layout); + return FastEquality.Equals(ref defaultValue, ref component, layout); + } + + public void AddSharedComponent(T component) where T : struct, ISharedComponentData + { + AddSharedComponent(Entity.Null, component); + } + + public void AddSharedComponent(Entity e, T component) where T : struct, ISharedComponentData + { + EnforceSingleThreadOwnership(); + int hashCode; + if (IsDefaultObject(ref component, out hashCode)) + AddEntitySharedComponentCommand(Command.AddSharedComponentData, e, hashCode, null); + else + AddEntitySharedComponentCommand(Command.AddSharedComponentData, e, hashCode, component); + } + + public void SetSharedComponent(T component) where T : struct, ISharedComponentData + { + SetSharedComponent(Entity.Null, component); + } + + public void SetSharedComponent(Entity e, T component) where T : struct, ISharedComponentData + { + EnforceSingleThreadOwnership(); + int hashCode; + if (IsDefaultObject(ref component, out hashCode)) + AddEntitySharedComponentCommand(Command.SetSharedComponentData, e, hashCode, null); + else + AddEntitySharedComponentCommand(Command.SetSharedComponentData, e, hashCode, component); + } + + private enum Command + { + CreateEntity, + DestroyEntity, + + AddComponent, + RemoveComponent, + RemoveSystemStateComponent, + SetComponent, + + AddSharedComponentData, + SetSharedComponentData + } + + /// + /// Play back all recorded operations against an entity manager. + /// + /// The entity manager that will receive the operations + public void Playback(EntityManager mgr) + { + if (mgr == null) + throw new NullReferenceException($"{nameof(mgr)} cannot be null"); + + EnforceSingleThreadOwnership(); + + var head = m_Data->m_Head; + var lastEntity = new Entity(); + + while (head != null) + { + var off = 0; + var buf = (byte*) head + sizeof(Chunk); + + while (off < head->Used) + { + var header = (BasicCommand*) (buf + off); + + switch ((Command) header->CommandType) + { + case Command.DestroyEntity: + { + mgr.DestroyEntity(((EntityCommand*) header)->Entity); + } + break; + + case Command.RemoveComponent: + { + var cmd = (EntityComponentCommand*) header; + var entity = cmd->Header.Entity == Entity.Null ? lastEntity : cmd->Header.Entity; + mgr.RemoveComponent(entity, TypeManager.GetType(cmd->ComponentTypeIndex)); + } + break; + + case Command.RemoveSystemStateComponent: + { + var cmd = (EntityComponentCommand*) header; + var entity = cmd->Header.Entity == Entity.Null ? lastEntity : cmd->Header.Entity; + mgr.RemoveSystemStateComponent(entity, TypeManager.GetType(cmd->ComponentTypeIndex)); + } + break; + + case Command.CreateEntity: + { + var cmd = (CreateCommand*) header; + if (cmd->Archetype.Valid) + lastEntity = mgr.CreateEntity(cmd->Archetype); + else + lastEntity = mgr.CreateEntity(); + break; + } + + case Command.AddComponent: + { + var cmd = (EntityComponentCommand*) header; + var componentType = (ComponentType) TypeManager.GetType(cmd->ComponentTypeIndex); + var entity = cmd->Header.Entity == Entity.Null ? lastEntity : cmd->Header.Entity; + mgr.AddComponent(entity, componentType); + mgr.SetComponentDataRaw(entity, cmd->ComponentTypeIndex, cmd + 1, cmd->ComponentSize); + } + break; + + case Command.SetComponent: + { + var cmd = (EntityComponentCommand*) header; + var entity = cmd->Header.Entity == Entity.Null ? lastEntity : cmd->Header.Entity; + mgr.SetComponentDataRaw(entity, cmd->ComponentTypeIndex, cmd + 1, cmd->ComponentSize); + } + break; + + case Command.AddSharedComponentData: + { + var cmd = (EntitySharedComponentCommand*) header; + var entity = cmd->Header.Entity == Entity.Null ? lastEntity : cmd->Header.Entity; + mgr.AddSharedComponentDataBoxed(entity, cmd->ComponentTypeIndex, cmd->HashCode, + cmd->GetBoxedObject()); + } + break; + + case Command.SetSharedComponentData: + { + var cmd = (EntitySharedComponentCommand*) header; + var entity = cmd->Header.Entity == Entity.Null ? lastEntity : cmd->Header.Entity; + mgr.SetSharedComponentDataBoxed(entity, cmd->ComponentTypeIndex, cmd->HashCode, + cmd->GetBoxedObject()); + } + break; + } + + off += header->TotalSize; + } + + head = head->Next; + } + } + } +} diff --git a/Unity.Entities/EntityCommandBuffer.cs.meta b/Unity.Entities/EntityCommandBuffer.cs.meta new file mode 100644 index 00000000..60eba9d7 --- /dev/null +++ b/Unity.Entities/EntityCommandBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 83c123dc35042db4b830308bc1eebb8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/EntityDataManager.cs b/Unity.Entities/EntityDataManager.cs new file mode 100644 index 00000000..fe287f72 --- /dev/null +++ b/Unity.Entities/EntityDataManager.cs @@ -0,0 +1,832 @@ +//#define USE_BURST_DESTROY + +using System; +using System.Diagnostics; +using Unity.Assertions; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal struct CleanupEntity : ISystemStateComponentData + { + } + + internal unsafe struct EntityDataManager + { +#if USE_BURST_DESTROY + private delegate Chunk* DeallocateDataEntitiesInChunkDelegate(EntityDataManager* entityDataManager, Entity* entities, int count, out int indexInChunk, out int batchCount); + static DeallocateDataEntitiesInChunkDelegate ms_DeallocateDataEntitiesInChunkDelegate; +#endif + + private struct EntityData + { + public int Version; + public Archetype* Archetype; + public Chunk* Chunk; + public int IndexInChunk; + } + + private EntityData* m_Entities; + private int m_EntitiesCapacity; + private int m_EntitiesFreeIndex; + + private int* m_ComponentTypeOrderVersion; + public uint GlobalSystemVersion; + + public int Version => GetComponentTypeOrderVersion(TypeManager.GetTypeIndex()); + + public void IncrementGlobalSystemVersion() + { + ChangeVersionUtility.IncrementGlobalSystemVersion(ref GlobalSystemVersion); + } + + public void OnCreate(int capacity) + { + m_EntitiesCapacity = capacity; + m_Entities = + (EntityData*) UnsafeUtility.Malloc(m_EntitiesCapacity * sizeof(EntityData), 64, Allocator.Persistent); + m_EntitiesFreeIndex = 0; + GlobalSystemVersion = ChangeVersionUtility.InitialGlobalSystemVersion; + InitializeAdditionalCapacity(0); + +#if USE_BURST_DESTROY + if (ms_DeallocateDataEntitiesInChunkDelegate == null) + { + ms_DeallocateDataEntitiesInChunkDelegate = DeallocateDataEntitiesInChunk; + ms_DeallocateDataEntitiesInChunkDelegate = + Burst.BurstDelegateCompiler.CompileDelegate(ms_DeallocateDataEntitiesInChunkDelegate); + } +#endif + + const int componentTypeOrderVersionSize = sizeof(int) * TypeManager.MaximumTypesCount; + m_ComponentTypeOrderVersion = (int*) UnsafeUtility.Malloc(componentTypeOrderVersionSize, + UnsafeUtility.AlignOf(), Allocator.Persistent); + UnsafeUtility.MemClear(m_ComponentTypeOrderVersion, componentTypeOrderVersionSize); + } + + public void OnDestroy() + { + UnsafeUtility.Free(m_Entities, Allocator.Persistent); + m_Entities = null; + m_EntitiesCapacity = 0; + + UnsafeUtility.Free(m_ComponentTypeOrderVersion, Allocator.Persistent); + m_ComponentTypeOrderVersion = null; + } + + private void InitializeAdditionalCapacity(int start) + { + for (var i = start; i != m_EntitiesCapacity; i++) + { + m_Entities[i].IndexInChunk = i + 1; + m_Entities[i].Version = 1; + m_Entities[i].Chunk = null; + } + + // Last entity indexInChunk identifies that we ran out of space... + m_Entities[m_EntitiesCapacity - 1].IndexInChunk = -1; + } + + private void IncreaseCapacity() + { + Capacity = 2 * Capacity; + } + + public int Capacity + { + get => m_EntitiesCapacity; + set + { + if (value <= m_EntitiesCapacity) + return; + + var newEntities = (EntityData*) UnsafeUtility.Malloc(value * sizeof(EntityData), + 64, Allocator.Persistent); + UnsafeUtility.MemCpy(newEntities, m_Entities, m_EntitiesCapacity * sizeof(EntityData)); + UnsafeUtility.Free(m_Entities, Allocator.Persistent); + + var startNdx = m_EntitiesCapacity - 1; + m_Entities = newEntities; + m_EntitiesCapacity = value; + + InitializeAdditionalCapacity(startNdx); + } + } + + public bool Exists(Entity entity) + { + var exists = m_Entities[entity.Index].Version == entity.Version; + return exists; + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + public void AssertEntitiesExist(Entity* entities, int count) + { + for (var i = 0; i != count; i++) + { + var entity = entities + i; + var exists = m_Entities[entity->Index].Version == entity->Version; + if (!exists) + throw new ArgumentException( + "All entities passed to EntityManager must exist. One of the entities has already been destroyed or was never created."); + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + public void AssertEntityHasComponent(Entity entity, ComponentType componentType) + { + if (HasComponent(entity, componentType)) + return; + + if (!Exists(entity)) + throw new ArgumentException("The Entity does not exist"); + + if (HasComponent(entity, componentType.TypeIndex)) + throw new ArgumentException( + $"The component typeof({componentType.GetManagedType()}) exists on the entity but the exact type {componentType} does not"); + + throw new ArgumentException($"{componentType} component has not been added to the entity."); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + public void AssertEntityHasComponent(Entity entity, int componentType) + { + if (HasComponent(entity, componentType)) + return; + + if (!Exists(entity)) + throw new ArgumentException("The entity does not exist"); + + throw new ArgumentException("The component has not been added to the entity."); + } + + private static Chunk* EntityChunkBatch(EntityDataManager* entityDataManager, Entity* entities, int count, + out int indexInChunk, out int batchCount) + { + /// This is optimized for the case where the array of entities are allocated contigously in the chunk + /// Thus the compacting of other elements can be batched + + // Calculate baseEntityIndex & chunk + var baseEntityIndex = entities[0].Index; + + var chunk = entityDataManager->m_Entities[baseEntityIndex].Chunk; + indexInChunk = entityDataManager->m_Entities[baseEntityIndex].IndexInChunk; + batchCount = 0; + + var entityDatas = entityDataManager->m_Entities; + + while (batchCount < count) + { + var entityIndex = entities[batchCount].Index; + var data = entityDatas + entityIndex; + + if (data->Chunk != chunk || data->IndexInChunk != indexInChunk + batchCount) + break; + + batchCount++; + } + + return chunk; + } + + private static void DeallocateDataEntitiesInChunk(EntityDataManager* entityDataManager, Entity* entities, + Chunk* chunk, int indexInChunk, int batchCount) + { + var freeIndex = entityDataManager->m_EntitiesFreeIndex; + var entityDatas = entityDataManager->m_Entities; + + for (var i = 0; i < batchCount; i++) + { + var entityIndex = entities[i].Index; + var data = entityDatas + entityIndex; + + data->Chunk = null; + data->Version++; + data->IndexInChunk = freeIndex; + freeIndex = entityIndex; + } + + entityDataManager->m_EntitiesFreeIndex = freeIndex; + + // We can just chop-off the end, no need to copy anything + if (chunk->Count == indexInChunk + batchCount) + return; + + // updates EntitityData->indexInChunk to point to where the components will be moved to + //Assert.IsTrue(chunk->archetype->sizeOfs[0] == sizeof(Entity) && chunk->archetype->offsets[0] == 0); + var movedEntities = (Entity*) chunk->Buffer + (chunk->Count - batchCount); + for (var i = 0; i != batchCount; i++) + entityDataManager->m_Entities[movedEntities[i].Index].IndexInChunk = indexInChunk + i; + + // Move component data from the end to where we deleted components + ChunkDataUtility.Copy(chunk, chunk->Count - batchCount, chunk, indexInChunk, batchCount); + } + + public static void FreeDataEntitiesInChunk(EntityDataManager* entityDataManager, Chunk* chunk, int count) + { + var freeIndex = entityDataManager->m_EntitiesFreeIndex; + var entityDatas = entityDataManager->m_Entities; + + var chunkEntities = (Entity*) chunk->Buffer; + + for (var i = 0; i != count; i++) + { + var entityIndex = chunkEntities[i].Index; + var data = entityDatas + entityIndex; + + data->Chunk = null; + data->Version++; + data->IndexInChunk = freeIndex; + freeIndex = entityIndex; + } + + entityDataManager->m_EntitiesFreeIndex = freeIndex; + } + + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public int CheckInternalConsistency() + { + var aliveEntities = 0; + var entityType = TypeManager.GetTypeIndex(); + + for (var i = 0; i != m_EntitiesCapacity; i++) + { + if (m_Entities[i].Chunk == null) + continue; + + aliveEntities++; + var archetype = m_Entities[i].Archetype; + Assert.AreEqual(entityType, archetype->Types[0].TypeIndex); + var entity = + *(Entity*) ChunkDataUtility.GetComponentDataRO(m_Entities[i].Chunk, m_Entities[i].IndexInChunk, 0); + Assert.AreEqual(i, entity.Index); + Assert.AreEqual(m_Entities[i].Version, entity.Version); + + Assert.IsTrue(Exists(entity)); + } + + return aliveEntities; + } +#endif + + public void AllocateEntities(Archetype* arch, Chunk* chunk, int baseIndex, int count, Entity* outputEntities) + { + Assert.AreEqual(chunk->Archetype->Offsets[0], 0); + Assert.AreEqual(chunk->Archetype->SizeOfs[0], sizeof(Entity)); + + var entityInChunkStart = (Entity*) chunk->Buffer + baseIndex; + + for (var i = 0; i != count; i++) + { + var entity = m_Entities + m_EntitiesFreeIndex; + if (entity->IndexInChunk == -1) + { + IncreaseCapacity(); + entity = m_Entities + m_EntitiesFreeIndex; + } + + outputEntities[i].Index = m_EntitiesFreeIndex; + outputEntities[i].Version = entity->Version; + + var entityInChunk = entityInChunkStart + i; + + entityInChunk->Index = m_EntitiesFreeIndex; + entityInChunk->Version = entity->Version; + + m_EntitiesFreeIndex = entity->IndexInChunk; + + entity->IndexInChunk = baseIndex + i; + entity->Archetype = arch; + entity->Chunk = chunk; + } + } + + public bool HasComponent(Entity entity, int type) + { + if (!Exists(entity)) + return false; + + var archetype = m_Entities[entity.Index].Archetype; + return ChunkDataUtility.GetIndexInTypeArray(archetype, type) != -1; + } + + public bool HasComponent(Entity entity, ComponentType type) + { + if (!Exists(entity)) + return false; + + var archetype = m_Entities[entity.Index].Archetype; + + if (!type.IsFixedArray) + return ChunkDataUtility.GetIndexInTypeArray(archetype, type.TypeIndex) != -1; + + var idx = ChunkDataUtility.GetIndexInTypeArray(archetype, type.TypeIndex); + if (idx == -1) + return false; + + return archetype->Types[idx].FixedArrayLength == type.FixedArrayLength; + } + + public byte* GetComponentDataWithTypeRO(Entity entity, int typeIndex) + { + var entityData = m_Entities + entity.Index; + return ChunkDataUtility.GetComponentDataWithTypeRO(entityData->Chunk, entityData->IndexInChunk, typeIndex); + } + + public byte* GetComponentDataWithTypeRW(Entity entity, int typeIndex, uint globalVersion) + { + var entityData = m_Entities + entity.Index; + return ChunkDataUtility.GetComponentDataWithTypeRW(entityData->Chunk, entityData->IndexInChunk, typeIndex, + globalVersion); + } + + public byte* GetComponentDataWithTypeRO(Entity entity, int typeIndex, ref int typeLookupCache) + { + var entityData = m_Entities + entity.Index; + return ChunkDataUtility.GetComponentDataWithTypeRO(entityData->Chunk, entityData->IndexInChunk, typeIndex, + ref typeLookupCache); + } + + public byte* GetComponentDataWithTypeRW(Entity entity, int typeIndex, uint globalVersion, + ref int typeLookupCache) + { + var entityData = m_Entities + entity.Index; + return ChunkDataUtility.GetComponentDataWithTypeRW(entityData->Chunk, entityData->IndexInChunk, typeIndex, + globalVersion, ref typeLookupCache); + } + + public void GetComponentDataWithTypeAndFixedArrayLength(Entity entity, int typeIndex, out byte* ptr, + out int fixedArrayLength, bool isWriting) + { + var entityData = m_Entities + entity.Index; + ChunkDataUtility.GetComponentDataWithTypeAndFixedArrayLength(entityData->Chunk, entityData->IndexInChunk, + typeIndex, out ptr, out fixedArrayLength); + } + + public Chunk* GetComponentChunk(Entity entity) + { + var entityData = m_Entities + entity.Index; + return entityData->Chunk; + } + + public void GetComponentChunk(Entity entity, out Chunk* chunk, out int chunkIndex) + { + var entityData = m_Entities + entity.Index; + chunk = entityData->Chunk; + chunkIndex = entityData->IndexInChunk; + } + + public Archetype* GetArchetype(Entity entity) + { + return m_Entities[entity.Index].Archetype; + } + + public void SetArchetype(ArchetypeManager typeMan, Entity entity, Archetype* archetype, + int* sharedComponentDataIndices) + { + var chunk = typeMan.GetChunkWithEmptySlots(archetype, sharedComponentDataIndices); + var chunkIndex = typeMan.AllocateIntoChunk(chunk); + + var oldArchetype = m_Entities[entity.Index].Archetype; + var oldChunk = m_Entities[entity.Index].Chunk; + var oldChunkIndex = m_Entities[entity.Index].IndexInChunk; + ChunkDataUtility.Convert(oldChunk, oldChunkIndex, chunk, chunkIndex); + if (chunk->ManagedArrayIndex >= 0 && oldChunk->ManagedArrayIndex >= 0) + ChunkDataUtility.CopyManagedObjects(typeMan, oldChunk, oldChunkIndex, chunk, chunkIndex, 1); + + m_Entities[entity.Index].Archetype = archetype; + m_Entities[entity.Index].Chunk = chunk; + m_Entities[entity.Index].IndexInChunk = chunkIndex; + + var lastIndex = oldChunk->Count - 1; + // No need to replace with ourselves + if (lastIndex != oldChunkIndex) + { + var lastEntity = (Entity*) ChunkDataUtility.GetComponentDataRO(oldChunk, lastIndex, 0); + m_Entities[lastEntity->Index].IndexInChunk = oldChunkIndex; + + ChunkDataUtility.Copy(oldChunk, lastIndex, oldChunk, oldChunkIndex, 1); + if (oldChunk->ManagedArrayIndex >= 0) + ChunkDataUtility.CopyManagedObjects(typeMan, oldChunk, lastIndex, oldChunk, oldChunkIndex, 1); + } + + if (oldChunk->ManagedArrayIndex >= 0) + ChunkDataUtility.ClearManagedObjects(typeMan, oldChunk, lastIndex, 1); + + --oldArchetype->EntityCount; + typeMan.SetChunkCount(oldChunk, lastIndex); + } + + public void AddComponent(Entity entity, ComponentType type, ArchetypeManager archetypeManager, + SharedComponentDataManager sharedComponentDataManager, + EntityGroupManager groupManager, ComponentTypeInArchetype* componentTypeInArchetypeArray) + { + var componentType = new ComponentTypeInArchetype(type); + var archetype = GetArchetype(entity); + + var t = 0; + while (t < archetype->TypesCount && archetype->Types[t] < componentType) + { + componentTypeInArchetypeArray[t] = archetype->Types[t]; + ++t; + } + + componentTypeInArchetypeArray[t] = componentType; + while (t < archetype->TypesCount) + { + componentTypeInArchetypeArray[t + 1] = archetype->Types[t]; + ++t; + } + + var newType = archetypeManager.GetOrCreateArchetype(componentTypeInArchetypeArray, + archetype->TypesCount + 1, groupManager); + + int* sharedComponentDataIndices = null; + if (newType->NumSharedComponents > 0) + { + var oldSharedComponentDataIndices = GetComponentChunk(entity)->SharedComponentValueArray; + var newComponentIsShared = TypeManager.TypeCategory.ISharedComponentData == + TypeManager.GetComponentType(type.TypeIndex).Category; + if (newComponentIsShared) + { + int* stackAlloced = stackalloc int[newType->NumSharedComponents]; + sharedComponentDataIndices = stackAlloced; + + if (archetype->SharedComponentOffset == null) + { + sharedComponentDataIndices[0] = 0; + } + else + { + t = 0; + var sharedIndex = 0; + while (t < archetype->TypesCount && archetype->Types[t] < componentType) + { + if (archetype->SharedComponentOffset[t] != -1) + { + sharedComponentDataIndices[sharedIndex] = oldSharedComponentDataIndices[sharedIndex]; + ++sharedIndex; + } + + ++t; + } + + sharedComponentDataIndices[sharedIndex] = 0; + while (t < archetype->TypesCount) + { + if (archetype->SharedComponentOffset[t] != -1) + { + sharedComponentDataIndices[sharedIndex + 1] = + oldSharedComponentDataIndices[sharedIndex]; + ++sharedIndex; + } + + ++t; + } + } + } + else + { + // reuse old sharedComponentDataIndices + sharedComponentDataIndices = oldSharedComponentDataIndices; + } + } + + SetArchetype(archetypeManager, entity, newType, sharedComponentDataIndices); + + IncrementComponentOrderVersion(newType, GetComponentChunk(entity), sharedComponentDataManager); + } + + private bool ArchetypeReadyToDeallocate(Archetype* archetype) + { + // Only Cleanup component remaining after removing public components, can delete + if (archetype->TypesCount == 2 && + archetype->Types[1].TypeIndex == TypeManager.GetTypeIndex()) return true; + + for (var t = 1; t < archetype->TypesCount; ++t) + { + var typeIndex = archetype->Types[t].TypeIndex; + if (typeof(ISystemStateComponentData).IsAssignableFrom(TypeManager.GetType(typeIndex))) return false; + } + + return true; + } + + public void TryRemoveEntityId(Entity* entities, int count, ArchetypeManager archetypeManager, + SharedComponentDataManager sharedComponentDataManager, + EntityGroupManager groupManager, ComponentTypeInArchetype* componentTypeInArchetypeArray) + { + var entityIndex = 0; + while (entityIndex != count) + { + int indexInChunk; + int batchCount; + fixed (EntityDataManager* manager = &this) + { + var chunk = EntityChunkBatch(manager, entities + entityIndex, count - entityIndex, out indexInChunk, + out batchCount); + var archetype = GetArchetype(entities[entityIndex]); + if (ArchetypeReadyToDeallocate(archetype)) + { + DeallocateDataEntitiesInChunk(manager, entities + entityIndex, chunk, indexInChunk, batchCount); + IncrementComponentOrderVersion(chunk->Archetype, chunk, sharedComponentDataManager); + + if (chunk->ManagedArrayIndex >= 0) + { + // We can just chop-off the end, no need to copy anything + if (chunk->Count != indexInChunk + batchCount) + ChunkDataUtility.CopyManagedObjects(archetypeManager, chunk, chunk->Count - batchCount, + chunk, + indexInChunk, batchCount); + + ChunkDataUtility.ClearManagedObjects(archetypeManager, chunk, chunk->Count - batchCount, + batchCount); + } + + chunk->Archetype->EntityCount -= batchCount; + archetypeManager.SetChunkCount(chunk, chunk->Count - batchCount); + } + else + { + for (var batchEntityIndex = 0; batchEntityIndex < batchCount; batchEntityIndex++) + { + var entity = entities[entityIndex + batchEntityIndex]; + var removedTypes = 0; + var removedComponentIsShared = false; + for (var t = 1; t < archetype->TypesCount; ++t) + { + var typeIndex = archetype->Types[t].TypeIndex; + if (!typeof(ISystemStateComponentData).IsAssignableFrom(TypeManager.GetType(typeIndex))) + { + ++removedTypes; + removedComponentIsShared |= TypeManager.TypeCategory.ISharedComponentData == + TypeManager.GetComponentType(typeIndex).Category; + } + else + { + componentTypeInArchetypeArray[t - removedTypes] = archetype->Types[t]; + } + } + + componentTypeInArchetypeArray[archetype->TypesCount - removedTypes] = + new ComponentTypeInArchetype(ComponentType.Create()); + + var newType = archetypeManager.GetOrCreateArchetype(componentTypeInArchetypeArray, + archetype->TypesCount - removedTypes + 1, groupManager); + + int* sharedComponentDataIndices = null; + if (newType->NumSharedComponents > 0) + { + var oldSharedComponentDataIndices = + GetComponentChunk(entity)->SharedComponentValueArray; + if (removedComponentIsShared) + { + int* tempAlloc = stackalloc int[newType->NumSharedComponents]; + sharedComponentDataIndices = tempAlloc; + + var srcIndex = 0; + var dstIndex = 0; + for (var t = 0; t < archetype->TypesCount; ++t) + if (archetype->SharedComponentOffset[t] != -1) + { + var typeIndex = archetype->Types[t].TypeIndex; + if (!typeof(ISystemStateComponentData).IsAssignableFrom( + TypeManager.GetType(typeIndex))) + { + srcIndex++; + } + else + { + sharedComponentDataIndices[dstIndex] = + oldSharedComponentDataIndices[srcIndex]; + srcIndex++; + dstIndex++; + } + } + } + else + { + // reuse old sharedComponentDataIndices + sharedComponentDataIndices = oldSharedComponentDataIndices; + } + } + + IncrementComponentOrderVersion(archetype, GetComponentChunk(entity), + sharedComponentDataManager); + SetArchetype(archetypeManager, entity, newType, sharedComponentDataIndices); + } + } + } + + entityIndex += batchCount; + } + } + + public void RemoveComponent(Entity entity, ComponentType type, ArchetypeManager archetypeManager, + SharedComponentDataManager sharedComponentDataManager, + EntityGroupManager groupManager, ComponentTypeInArchetype* componentTypeInArchetypeArray) + { + var componentType = new ComponentTypeInArchetype(type); + + var archetype = GetArchetype(entity); + + var removedTypes = 0; + for (var t = 0; t < archetype->TypesCount; ++t) + if (archetype->Types[t].TypeIndex == componentType.TypeIndex) + ++removedTypes; + else + componentTypeInArchetypeArray[t - removedTypes] = archetype->Types[t]; + + Assert.AreNotEqual(-1, removedTypes); + + var newType = archetypeManager.GetOrCreateArchetype(componentTypeInArchetypeArray, + archetype->TypesCount - removedTypes, groupManager); + + int* sharedComponentDataIndices = null; + if (newType->NumSharedComponents > 0) + { + var oldSharedComponentDataIndices = GetComponentChunk(entity)->SharedComponentValueArray; + var removedComponentIsShared = TypeManager.TypeCategory.ISharedComponentData == + TypeManager.GetComponentType(type.TypeIndex).Category; + removedTypes = 0; + if (removedComponentIsShared) + { + int* tempAlloc = stackalloc int[newType->NumSharedComponents]; + sharedComponentDataIndices = tempAlloc; + + var srcIndex = 0; + var dstIndex = 0; + for (var t = 0; t < archetype->TypesCount; ++t) + if (archetype->SharedComponentOffset[t] != -1) + { + if (archetype->Types[t].TypeIndex == componentType.TypeIndex) + { + srcIndex++; + } + else + { + sharedComponentDataIndices[dstIndex] = oldSharedComponentDataIndices[srcIndex]; + srcIndex++; + dstIndex++; + } + } + } + else + { + // reuse old sharedComponentDataIndices + sharedComponentDataIndices = oldSharedComponentDataIndices; + } + } + + IncrementComponentOrderVersion(archetype, GetComponentChunk(entity), sharedComponentDataManager); + + SetArchetype(archetypeManager, entity, newType, sharedComponentDataIndices); + } + + public void MoveEntityToChunk(ArchetypeManager typeMan, Entity entity, Chunk* newChunk, int newChunkIndex) + { + var oldChunk = m_Entities[entity.Index].Chunk; + Assert.IsTrue(oldChunk->Archetype == newChunk->Archetype); + + var oldChunkIndex = m_Entities[entity.Index].IndexInChunk; + + ChunkDataUtility.Copy(oldChunk, oldChunkIndex, newChunk, newChunkIndex, 1); + + if (oldChunk->ManagedArrayIndex >= 0) + ChunkDataUtility.CopyManagedObjects(typeMan, oldChunk, oldChunkIndex, newChunk, newChunkIndex, 1); + + m_Entities[entity.Index].Chunk = newChunk; + m_Entities[entity.Index].IndexInChunk = newChunkIndex; + + var lastIndex = oldChunk->Count - 1; + // No need to replace with ourselves + if (lastIndex != oldChunkIndex) + { + var lastEntity = (Entity*) ChunkDataUtility.GetComponentDataRO(oldChunk, lastIndex, 0); + m_Entities[lastEntity->Index].IndexInChunk = oldChunkIndex; + + ChunkDataUtility.Copy(oldChunk, lastIndex, oldChunk, oldChunkIndex, 1); + if (oldChunk->ManagedArrayIndex >= 0) + ChunkDataUtility.CopyManagedObjects(typeMan, oldChunk, lastIndex, oldChunk, oldChunkIndex, 1); + } + + if (oldChunk->ManagedArrayIndex >= 0) + ChunkDataUtility.ClearManagedObjects(typeMan, oldChunk, lastIndex, 1); + + newChunk->Archetype->EntityCount--; + typeMan.SetChunkCount(oldChunk, oldChunk->Count - 1); + } + + public void CreateEntities(ArchetypeManager archetypeManager, Archetype* archetype, Entity* entities, int count) + { + while (count != 0) + { + var chunk = archetypeManager.GetChunkWithEmptySlots(archetype, null); + int allocatedIndex; + var allocatedCount = archetypeManager.AllocateIntoChunk(chunk, count, out allocatedIndex); + AllocateEntities(archetype, chunk, allocatedIndex, allocatedCount, entities); + ChunkDataUtility.ClearComponents(chunk, allocatedIndex, allocatedCount); + + entities += allocatedCount; + count -= allocatedCount; + } + + IncrementComponentTypeOrderVersion(archetype); + } + + public void InstantiateEntities(ArchetypeManager archetypeManager, + SharedComponentDataManager sharedComponentDataManager, Entity srcEntity, Entity* outputEntities, int count) + { + var srcIndex = m_Entities[srcEntity.Index].IndexInChunk; + var srcChunk = m_Entities[srcEntity.Index].Chunk; + var srcArchetype = m_Entities[srcEntity.Index].Archetype; + var srcSharedComponentDataIndices = GetComponentChunk(srcEntity)->SharedComponentValueArray; + + while (count != 0) + { + var chunk = archetypeManager.GetChunkWithEmptySlots(srcArchetype, srcSharedComponentDataIndices); + int indexInChunk; + var allocatedCount = archetypeManager.AllocateIntoChunk(chunk, count, out indexInChunk); + + ChunkDataUtility.ReplicateComponents(srcChunk, srcIndex, chunk, indexInChunk, allocatedCount); + + AllocateEntities(srcArchetype, chunk, indexInChunk, allocatedCount, outputEntities); + + outputEntities += allocatedCount; + count -= allocatedCount; + } + + IncrementComponentOrderVersion(srcArchetype, srcChunk, sharedComponentDataManager); + } + + public int GetSharedComponentDataIndex(Entity entity, int typeIndex) + { + var archetype = GetArchetype(entity); + var indexInTypeArray = ChunkDataUtility.GetIndexInTypeArray(archetype, typeIndex); + + var chunk = m_Entities[entity.Index].Chunk; + var sharedComponentValueArray = chunk->SharedComponentValueArray; + var sharedComponentOffset = m_Entities[entity.Index].Archetype->SharedComponentOffset[indexInTypeArray]; + return sharedComponentValueArray[sharedComponentOffset]; + } + + public void SetSharedComponentDataIndex(ArchetypeManager archetypeManager, + SharedComponentDataManager sharedComponentDataManager, Entity entity, int typeIndex, + int newSharedComponentDataIndex) + { + var archetype = GetArchetype(entity); + + var indexInTypeArray = ChunkDataUtility.GetIndexInTypeArray(archetype, typeIndex); + + var srcChunk = GetComponentChunk(entity); + var srcSharedComponentValueArray = srcChunk->SharedComponentValueArray; + var sharedComponentOffset = archetype->SharedComponentOffset[indexInTypeArray]; + var oldSharedComponentDataIndex = srcSharedComponentValueArray[sharedComponentOffset]; + + if (newSharedComponentDataIndex == oldSharedComponentDataIndex) + return; + + var sharedComponentIndices = stackalloc int[archetype->NumSharedComponents]; + var srcSharedComponentDataIndices = srcChunk->SharedComponentValueArray; + + ArchetypeManager.CopySharedComponentDataIndexArray(sharedComponentIndices, srcSharedComponentDataIndices, + archetype->NumSharedComponents); + sharedComponentIndices[sharedComponentOffset] = newSharedComponentDataIndex; + + var newChunk = archetypeManager.GetChunkWithEmptySlots(archetype, sharedComponentIndices); + var newChunkIndex = archetypeManager.AllocateIntoChunk(newChunk); + + IncrementComponentOrderVersion(archetype, srcChunk, sharedComponentDataManager); + + MoveEntityToChunk(archetypeManager, entity, newChunk, newChunkIndex); + } + + private void IncrementComponentOrderVersion(Archetype* archetype, Chunk* chunk, + SharedComponentDataManager sharedComponentDataManager) + { + // Increment shared component version + var sharedComponentDataIndices = chunk->SharedComponentValueArray; + for (var i = 0; i < archetype->NumSharedComponents; i++) + sharedComponentDataManager.IncrementSharedComponentVersion(sharedComponentDataIndices[i]); + + IncrementComponentTypeOrderVersion(archetype); + } + + private void IncrementComponentTypeOrderVersion(Archetype* archetype) + { + // Increment type component version + for (var t = 0; t < archetype->TypesCount; ++t) + { + var typeIndex = archetype->Types[t].TypeIndex; + m_ComponentTypeOrderVersion[typeIndex]++; + } + } + + public int GetComponentTypeOrderVersion(int typeIndex) + { + return m_ComponentTypeOrderVersion[typeIndex]; + } + } +} diff --git a/Unity.Entities/EntityDataManager.cs.meta b/Unity.Entities/EntityDataManager.cs.meta new file mode 100644 index 00000000..96e9d551 --- /dev/null +++ b/Unity.Entities/EntityDataManager.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 1d934d7fe8be14d9891ba4cabc32b57c +timeCreated: 1504638216 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/EntityManager.cs b/Unity.Entities/EntityManager.cs new file mode 100644 index 00000000..3f6f49a6 --- /dev/null +++ b/Unity.Entities/EntityManager.cs @@ -0,0 +1,894 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Unity.Assertions; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using UnityEngine.Scripting; + +[assembly: InternalsVisibleTo("Unity.Entities.Hybrid")] +[assembly: InternalsVisibleTo("Unity.Entities.Properties")] + +namespace Unity.Entities +{ + //@TODO: There is nothing prevent non-main thread (non-job thread) access of EntityMnaager. + // Static Analysis or runtime checks? + + //@TODO: safety? + public unsafe struct EntityArchetype + { + [NativeDisableUnsafePtrRestriction] internal Archetype* Archetype; + + public bool Valid => Archetype != null; + + public static bool operator ==(EntityArchetype lhs, EntityArchetype rhs) + { + return lhs.Archetype == rhs.Archetype; + } + + public static bool operator !=(EntityArchetype lhs, EntityArchetype rhs) + { + return lhs.Archetype != rhs.Archetype; + } + + public override bool Equals(object compare) + { + return this == (EntityArchetype) compare; + } + + public override int GetHashCode() + { + return (int) Archetype; + } + + public int ChunkCount => Archetype->ChunkCount; + } + + public struct Entity : IEquatable + { + public int Index; + public int Version; + + public static bool operator ==(Entity lhs, Entity rhs) + { + return lhs.Index == rhs.Index && lhs.Version == rhs.Version; + } + + public static bool operator !=(Entity lhs, Entity rhs) + { + return lhs.Index != rhs.Index || lhs.Version != rhs.Version; + } + + public override bool Equals(object compare) + { + return this == (Entity) compare; + } + + public override int GetHashCode() + { + return Index; + } + + public static Entity Null => new Entity(); + + public bool Equals(Entity entity) + { + return entity.Index == Index && entity.Version == Version; + } + } + + [Preserve] + public sealed unsafe class EntityManager : ScriptBehaviourManager + { + EntityDataManager* m_Entities; + + ArchetypeManager m_ArchetypeManager; + EntityGroupManager m_GroupManager; + + SharedComponentDataManager m_SharedComponentManager; + + ExclusiveEntityTransaction m_ExclusiveEntityTransaction; + + ComponentType* m_CachedComponentTypeArray; + ComponentTypeInArchetype* m_CachedComponentTypeInArchetypeArray; + + internal object m_CachedComponentList; + + internal EntityDataManager* Entities + { + get => m_Entities; + private set => m_Entities = value; + } + + internal ArchetypeManager ArchetypeManager + { + get => m_ArchetypeManager; + private set => m_ArchetypeManager = value; + } + + public int Version => IsCreated ? m_Entities->Version : 0; + + public uint GlobalSystemVersion => IsCreated ? Entities->GlobalSystemVersion : 0; + + public bool IsCreated => m_CachedComponentTypeArray != null; + + public int EntityCapacity + { + get => Entities->Capacity; + set + { + BeforeStructuralChange(); + Entities->Capacity = value; + } + } + + internal ComponentJobSafetyManager ComponentJobSafetyManager { get; private set; } + + public JobHandle ExclusiveEntityTransactionDependency + { + get => ComponentJobSafetyManager.ExclusiveTransactionDependency; + set => ComponentJobSafetyManager.ExclusiveTransactionDependency = value; + } + + EntityManagerDebug m_Debug; + + internal EntityManagerDebug Debug => m_Debug ?? (m_Debug = new EntityManagerDebug(this)); + + protected override void OnBeforeCreateManagerInternal(World world, int capacity) + { + } + + protected override void OnBeforeDestroyManagerInternal() + { + } + + protected override void OnAfterDestroyManagerInternal() + { + } + + protected override void OnCreateManager(int capacity) + { + TypeManager.Initialize(); + + Entities = (EntityDataManager*) UnsafeUtility.Malloc(sizeof(EntityDataManager), 64, Allocator.Persistent); + Entities->OnCreate(capacity); + + m_SharedComponentManager = new SharedComponentDataManager(); + + ArchetypeManager = new ArchetypeManager(m_SharedComponentManager); + ComponentJobSafetyManager = new ComponentJobSafetyManager(); + m_GroupManager = new EntityGroupManager(ComponentJobSafetyManager); + + m_ExclusiveEntityTransaction = new ExclusiveEntityTransaction(ArchetypeManager, m_GroupManager, + m_SharedComponentManager, Entities); + + m_CachedComponentTypeArray = + (ComponentType*) UnsafeUtility.Malloc(sizeof(ComponentType) * 32 * 1024, 16, Allocator.Persistent); + m_CachedComponentTypeInArchetypeArray = + (ComponentTypeInArchetype*) UnsafeUtility.Malloc(sizeof(ComponentTypeInArchetype) * 32 * 1024, 16, + Allocator.Persistent); + } + + protected override void OnDestroyManager() + { + EndExclusiveEntityTransaction(); + + ComponentJobSafetyManager.Dispose(); + ComponentJobSafetyManager = null; + + Entities->OnDestroy(); + UnsafeUtility.Free(Entities, Allocator.Persistent); + Entities = null; + ArchetypeManager.Dispose(); + ArchetypeManager = null; + m_GroupManager.Dispose(); + m_GroupManager = null; + m_ExclusiveEntityTransaction.OnDestroyManager(); + + m_SharedComponentManager.Dispose(); + + UnsafeUtility.Free(m_CachedComponentTypeArray, Allocator.Persistent); + m_CachedComponentTypeArray = null; + + UnsafeUtility.Free(m_CachedComponentTypeInArchetypeArray, Allocator.Persistent); + m_CachedComponentTypeInArchetypeArray = null; + } + + internal override void InternalUpdate() + { + } + + private int PopulatedCachedTypeArray(ComponentType* requiredComponents, int count) + { + m_CachedComponentTypeArray[0] = ComponentType.Create(); + for (var i = 0; i < count; ++i) + SortingUtilities.InsertSorted(m_CachedComponentTypeArray, i + 1, requiredComponents[i]); + return count + 1; + } + + private int PopulatedCachedTypeInArchetypeArray(ComponentType* requiredComponents, int count) + { + m_CachedComponentTypeInArchetypeArray[0] = new ComponentTypeInArchetype(ComponentType.Create()); + for (var i = 0; i < count; ++i) + SortingUtilities.InsertSorted(m_CachedComponentTypeInArchetypeArray, i + 1, requiredComponents[i]); + return count + 1; + } + + internal ComponentGroup CreateComponentGroup(ComponentType* requiredComponents, int count) + { + var typeArrayCount = PopulatedCachedTypeArray(requiredComponents, count); + var grp = m_GroupManager.CreateEntityGroupIfCached(ArchetypeManager, Entities, + m_CachedComponentTypeArray, typeArrayCount); + if (grp != null) + return grp; + + BeforeStructuralChange(); + + return m_GroupManager.CreateEntityGroup(ArchetypeManager, Entities, m_CachedComponentTypeArray, + typeArrayCount); + } + + internal ComponentGroup CreateComponentGroup(params ComponentType[] requiredComponents) + { + fixed (ComponentType* requiredComponentsPtr = requiredComponents) + { + return CreateComponentGroup(requiredComponentsPtr, requiredComponents.Length); + } + } + + internal EntityArchetype CreateArchetype(ComponentType* types, int count) + { + var cachedComponentCount = PopulatedCachedTypeInArchetypeArray(types, count); + + // Lookup existing archetype (cheap) + EntityArchetype entityArchetype; + entityArchetype.Archetype = + ArchetypeManager.GetExistingArchetype(m_CachedComponentTypeInArchetypeArray, cachedComponentCount); + if (entityArchetype.Archetype != null) + return entityArchetype; + + // Creating an archetype invalidates all iterators / jobs etc + // because it affects the live iteration linked lists... + BeforeStructuralChange(); + + entityArchetype.Archetype = ArchetypeManager.GetOrCreateArchetype(m_CachedComponentTypeInArchetypeArray, + cachedComponentCount, m_GroupManager); + return entityArchetype; + } + + public EntityArchetype CreateArchetype(params ComponentType[] types) + { + fixed (ComponentType* typesPtr = types) + { + return CreateArchetype(typesPtr, types.Length); + } + } + + public void CreateEntity(EntityArchetype archetype, NativeArray entities) + { + CreateEntityInternal(archetype, (Entity*) entities.GetUnsafePtr(), entities.Length); + } + + public Entity CreateEntity(EntityArchetype archetype) + { + Entity entity; + CreateEntityInternal(archetype, &entity, 1); + return entity; + } + + public Entity CreateEntity(params ComponentType[] types) + { + return CreateEntity(CreateArchetype(types)); + } + + private void CreateEntityInternal(EntityArchetype archetype, Entity* entities, int count) + { + BeforeStructuralChange(); + Entities->CreateEntities(ArchetypeManager, archetype.Archetype, entities, count); + } + + public void DestroyEntity(ComponentGroup componentGroupFilter) + { + BeforeStructuralChange(); + + // @TODO: Don't copy entity array, + // take advantage of inherent chunk structure to do faster destruction + var entityGroupArray = componentGroupFilter.GetEntityArray(); + if (entityGroupArray.Length == 0) + return; + + var entityArray = new NativeArray(entityGroupArray.Length, Allocator.Temp, + NativeArrayOptions.UninitializedMemory); + entityGroupArray.CopyTo(entityArray); + if (entityArray.Length != 0) + Entities->TryRemoveEntityId((Entity*) entityArray.GetUnsafeReadOnlyPtr(), entityArray.Length, + ArchetypeManager, m_SharedComponentManager, m_GroupManager, m_CachedComponentTypeInArchetypeArray); + + entityArray.Dispose(); + } + + public void DestroyEntity(NativeArray entities) + { + DestroyEntityInternal((Entity*) entities.GetUnsafeReadOnlyPtr(), entities.Length); + } + + public void DestroyEntity(NativeSlice entities) + { + DestroyEntityInternal((Entity*) entities.GetUnsafeReadOnlyPtr(), entities.Length); + } + + public void DestroyEntity(Entity entity) + { + DestroyEntityInternal(&entity, 1); + } + + private void DestroyEntityInternal(Entity* entities, int count) + { + BeforeStructuralChange(); + Entities->AssertEntitiesExist(entities, count); + Entities->TryRemoveEntityId(entities, count, ArchetypeManager, m_SharedComponentManager, m_GroupManager, + m_CachedComponentTypeInArchetypeArray); + } + + public bool Exists(Entity entity) + { + return Entities->Exists(entity); + } + + public bool HasComponent(Entity entity) + { + return Entities->HasComponent(entity, ComponentType.Create()); + } + + public bool HasComponent(Entity entity, ComponentType type) + { + return Entities->HasComponent(entity, type); + } + + public Entity Instantiate(Entity srcEntity) + { + Entity entity; + InstantiateInternal(srcEntity, &entity, 1); + return entity; + } + + public void Instantiate(Entity srcEntity, NativeArray outputEntities) + { + InstantiateInternal(srcEntity, (Entity*) outputEntities.GetUnsafePtr(), outputEntities.Length); + } + + internal void InstantiateInternal(Entity srcEntity, Entity* outputEntities, int count) + { + BeforeStructuralChange(); + if (!Entities->Exists(srcEntity)) + throw new ArgumentException("srcEntity is not a valid entity"); + + Entities->InstantiateEntities(ArchetypeManager, m_SharedComponentManager, srcEntity, outputEntities, + count); + } + + public void AddComponent(Entity entity, ComponentType type) + { + BeforeStructuralChange(); + Entities->AssertEntitiesExist(&entity, 1); + Entities->AddComponent(entity, type, ArchetypeManager, m_SharedComponentManager, m_GroupManager, + m_CachedComponentTypeInArchetypeArray); + } + + public void RemoveComponents(Entity entity) + { + BeforeStructuralChange(); + Entities->TryRemoveEntityId(&entity, 1, ArchetypeManager, m_SharedComponentManager, m_GroupManager, + m_CachedComponentTypeInArchetypeArray); + } + + public void RemoveComponent(Entity entity, ComponentType type) + { + if (typeof(ISystemStateComponentData).IsAssignableFrom(type.GetManagedType())) + throw new ArgumentException( + $"RemoveComponent<{type.GetManagedType()}> cannot be called on ISystemStateComponentData"); + + BeforeStructuralChange(); + Entities->AssertEntityHasComponent(entity, type); + Entities->RemoveComponent(entity, type, ArchetypeManager, m_SharedComponentManager, m_GroupManager, + m_CachedComponentTypeInArchetypeArray); + } + + public void RemoveComponent(Entity entity) + { + RemoveComponent(entity, ComponentType.Create()); + } + + public void RemoveSystemStateComponent(Entity entity, ComponentType type) + { + if (!typeof(ISystemStateComponentData).IsAssignableFrom(type.GetManagedType())) + throw new ArgumentException( + $"RemoveSystemStateComponent<{type.GetManagedType()}> cannot be called on IComponentData"); + + BeforeStructuralChange(); + + Entities->AssertEntityHasComponent(entity, type); + Entities->RemoveComponent(entity, type, ArchetypeManager, m_SharedComponentManager, m_GroupManager, + m_CachedComponentTypeInArchetypeArray); + Entities->TryRemoveEntityId(&entity, 1, ArchetypeManager, m_SharedComponentManager, m_GroupManager, + m_CachedComponentTypeInArchetypeArray); + } + + public void RemoveSystemStateComponent(Entity entity) + { + RemoveSystemStateComponent(entity, ComponentType.Create()); + } + + public void AddComponentData(Entity entity, T componentData) where T : struct, IComponentData + { + AddComponent(entity, ComponentType.Create()); + SetComponentData(entity, componentData); + } + + public ComponentDataFromEntity GetComponentDataFromEntity(bool isReadOnly = false) + where T : struct, IComponentData + { + var typeIndex = TypeManager.GetTypeIndex(); + return GetComponentDataFromEntity(typeIndex, isReadOnly); + } + + internal ComponentDataFromEntity GetComponentDataFromEntity(int typeIndex, bool isReadOnly) + where T : struct, IComponentData + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + return new ComponentDataFromEntity(typeIndex, Entities, + ComponentJobSafetyManager.GetSafetyHandle(typeIndex, isReadOnly)); +#else + return new ComponentDataFromEntity(typeIndex, m_Entities); +#endif + } + + internal FixedArrayFromEntity GetFixedArrayFromEntity(int typeIndex, bool isReadOnly = false) + where T : struct + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + return new FixedArrayFromEntity(typeIndex, Entities, isReadOnly, + ComponentJobSafetyManager.GetSafetyHandle(typeIndex, isReadOnly)); +#else + return new FixedArrayFromEntity(typeIndex, m_Entities, isReadOnly); +#endif + } + + public T GetComponentData(Entity entity) where T : struct, IComponentData + { + var typeIndex = TypeManager.GetTypeIndex(); + Entities->AssertEntityHasComponent(entity, typeIndex); + ComponentJobSafetyManager.CompleteWriteDependency(typeIndex); + + var ptr = Entities->GetComponentDataWithTypeRO(entity, typeIndex); + + T value; + UnsafeUtility.CopyPtrToStructure(ptr, out value); + return value; + } + + public void SetComponentData(Entity entity, T componentData) where T : struct, IComponentData + { + var typeIndex = TypeManager.GetTypeIndex(); + Entities->AssertEntityHasComponent(entity, typeIndex); + + ComponentJobSafetyManager.CompleteReadAndWriteDependency(typeIndex); + + var ptr = Entities->GetComponentDataWithTypeRW(entity, typeIndex, Entities->GlobalSystemVersion); + UnsafeUtility.CopyStructureToPtr(ref componentData, ptr); + } + + internal void SetComponentObject(Entity entity, ComponentType componentType, object componentObject) + { + Entities->AssertEntityHasComponent(entity, componentType.TypeIndex); + + //@TODO + Chunk* chunk; + int chunkIndex; + Entities->GetComponentChunk(entity, out chunk, out chunkIndex); + ArchetypeManager.SetManagedObject(chunk, componentType, chunkIndex, componentObject); + } + + public void GetAllUniqueSharedComponentDatas(List sharedComponentValues) + where T : struct, ISharedComponentData + { + m_SharedComponentManager.GetAllUniqueSharedComponents(sharedComponentValues); + } + + public T GetSharedComponentData(Entity entity) where T : struct, ISharedComponentData + { + var typeIndex = TypeManager.GetTypeIndex(); + Entities->AssertEntityHasComponent(entity, typeIndex); + + var sharedComponentIndex = Entities->GetSharedComponentDataIndex(entity, typeIndex); + return m_SharedComponentManager.GetSharedComponentData(sharedComponentIndex); + } + + public void AddSharedComponentData(Entity entity, T componentData) where T : struct, ISharedComponentData + { + //TODO: optimize this (no need to move the entity to a new chunk twice) + AddComponent(entity, ComponentType.Create()); + SetSharedComponentData(entity, componentData); + } + + internal void AddSharedComponentDataBoxed(Entity entity, int typeIndex, int hashCode, object componentData) + { + //TODO: optimize this (no need to move the entity to a new chunk twice) + AddComponent(entity, ComponentType.FromTypeIndex(typeIndex)); + SetSharedComponentDataBoxed(entity, typeIndex, hashCode, componentData); + } + + public void SetSharedComponentData(Entity entity, T componentData) where T : struct, ISharedComponentData + { + BeforeStructuralChange(); + + var typeIndex = TypeManager.GetTypeIndex(); + Entities->AssertEntityHasComponent(entity, typeIndex); + + var newSharedComponentDataIndex = m_SharedComponentManager.InsertSharedComponent(componentData); + Entities->SetSharedComponentDataIndex(ArchetypeManager, m_SharedComponentManager, entity, typeIndex, + newSharedComponentDataIndex); + m_SharedComponentManager.RemoveReference(newSharedComponentDataIndex); + } + + internal void SetSharedComponentDataBoxed(Entity entity, int typeIndex, int hashCode, object componentData) + { + BeforeStructuralChange(); + + Entities->AssertEntityHasComponent(entity, typeIndex); + + var newSharedComponentDataIndex = 0; + if (componentData != null) // null means default + newSharedComponentDataIndex = m_SharedComponentManager.InsertSharedComponentAssumeNonDefault(typeIndex, + hashCode, componentData, TypeManager.GetComponentType(typeIndex).FastEqualityLayout); + + Entities->SetSharedComponentDataIndex(ArchetypeManager, m_SharedComponentManager, entity, typeIndex, + newSharedComponentDataIndex); + m_SharedComponentManager.RemoveReference(newSharedComponentDataIndex); + } + + public NativeArray GetFixedArray(Entity entity) where T : struct + { + var typeIndex = TypeManager.GetTypeIndex(); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + Entities->AssertEntityHasComponent(entity, typeIndex); + if (TypeManager.GetComponentType().Category != TypeManager.TypeCategory.OtherValueType) + throw new ArgumentException( + $"GetComponentFixedArray<{typeof(T)}> may not be IComponentData or ISharedComponentData"); +#endif + + ComponentJobSafetyManager.CompleteWriteDependency(typeIndex); + + byte* ptr; + int length; + Entities->GetComponentDataWithTypeAndFixedArrayLength(entity, typeIndex, out ptr, out length, true); + + var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, length, Allocator.Invalid); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, + ComponentJobSafetyManager.GetSafetyHandle(typeIndex, false)); +#endif + + return array; + } + + public NativeArray GetAllEntities(Allocator allocator = Allocator.Temp) + { + BeforeStructuralChange(); + + var entityGroup = CreateComponentGroup(); + var groupArray = entityGroup.GetEntityArray(); + + var array = new NativeArray(groupArray.Length, allocator); + groupArray.CopyTo(array); + return array; + } + + public NativeArray GetComponentTypes(Entity entity, Allocator allocator = Allocator.Temp) + { + Entities->AssertEntitiesExist(&entity, 1); + + var archetype = Entities->GetArchetype(entity); + + var components = new NativeArray(archetype->TypesCount - 1, allocator); + + for (var i = 1; i < archetype->TypesCount; i++) + components[i - 1] = archetype->Types[i].ToComponentType(); + + return components; + } + + public int GetComponentCount(Entity entity) + { + Entities->AssertEntitiesExist(&entity, 1); + var archetype = Entities->GetArchetype(entity); + return archetype->TypesCount - 1; + } + + internal int GetComponentTypeIndex(Entity entity, int index) + { + Entities->AssertEntitiesExist(&entity, 1); + var archetype = Entities->GetArchetype(entity); + + if ((uint) index >= archetype->TypesCount) return -1; + + return archetype->Types[index + 1].TypeIndex; + } + + internal void SetComponentDataRaw(Entity entity, int typeIndex, void* data, int size) + { + Entities->AssertEntityHasComponent(entity, typeIndex); + + ComponentJobSafetyManager.CompleteReadAndWriteDependency(typeIndex); + + var ptr = Entities->GetComponentDataWithTypeRW(entity, typeIndex, Entities->GlobalSystemVersion); + UnsafeUtility.MemCpy(ptr, data, size); + } + + internal void* GetComponentDataRawRW(Entity entity, int typeIndex) + { + Entities->AssertEntityHasComponent(entity, typeIndex); + + ComponentJobSafetyManager.CompleteReadAndWriteDependency(typeIndex); + + var ptr = Entities->GetComponentDataWithTypeRW(entity, typeIndex, Entities->GlobalSystemVersion); + return ptr; + } + + internal object GetSharedComponentData(Entity entity, int typeIndex) + { + Entities->AssertEntityHasComponent(entity, typeIndex); + + var sharedComponentIndex = Entities->GetSharedComponentDataIndex(entity, typeIndex); + return m_SharedComponentManager.GetSharedComponentDataBoxed(sharedComponentIndex); + } + + public int GetComponentOrderVersion() + { + return Entities->GetComponentTypeOrderVersion(TypeManager.GetTypeIndex()); + } + + public int GetSharedComponentOrderVersion(T sharedComponent) where T : struct, ISharedComponentData + { + return m_SharedComponentManager.GetSharedComponentVersion(sharedComponent); + } + + public ExclusiveEntityTransaction BeginExclusiveEntityTransaction() + { + ComponentJobSafetyManager.BeginExclusiveTransaction(); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_ExclusiveEntityTransaction.SetAtomicSafetyHandle(ComponentJobSafetyManager.ExclusiveTransactionSafety); +#endif + return m_ExclusiveEntityTransaction; + } + + public void EndExclusiveEntityTransaction() + { + ComponentJobSafetyManager.EndExclusiveTransaction(); + } + + private void BeforeStructuralChange() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (ComponentJobSafetyManager.IsInTransaction) + throw new InvalidOperationException( + "Access to EntityManager is not allowed after EntityManager.BeginExclusiveEntityTransaction(); has been called."); +#endif + ComponentJobSafetyManager.CompleteAllJobsAndInvalidateArrays(); + } + + //@TODO: Not clear to me what this method is really for... + public void CompleteAllJobs() + { + ComponentJobSafetyManager.CompleteAllJobsAndInvalidateArrays(); + } + + public void MoveEntitiesFrom(EntityManager srcEntities) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (srcEntities == this) + throw new ArgumentException("srcEntities must not be the same as this EntityManager."); +#endif + + BeforeStructuralChange(); + srcEntities.BeforeStructuralChange(); + + ArchetypeManager.MoveChunks(srcEntities.ArchetypeManager, srcEntities.Entities, + srcEntities.m_SharedComponentManager, ArchetypeManager, m_GroupManager, m_SharedComponentManager, + Entities, m_SharedComponentManager); + + //@TODO: Need to incrmeent the component versions based the moved chunks... + } + + public void CheckInternalConsistency() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + + //@TODO: Validate from perspective of componentgroup... + //@TODO: Validate shared component data refcounts... + var entityCountEntityData = Entities->CheckInternalConsistency(); + var entityCountArchetypeManager = ArchetypeManager.CheckInternalConsistency(); + + Assert.AreEqual(entityCountEntityData, entityCountArchetypeManager); +#endif + } + + public List GetAssignableComponentTypes(Type interfaceType) + { + // #todo Cache this. It only can change when TypeManager.GetTypeCount() changes + var componentTypeCount = TypeManager.GetTypeCount(); + var assignableTypes = new List(); + for (var i = 0; i < componentTypeCount; i++) + { + var type = TypeManager.GetType(i); + if (interfaceType.IsAssignableFrom(type)) assignableTypes.Add(type); + } + + return assignableTypes; + } + + private bool TestMatchingArchetypeAny(Archetype* archetype, ComponentType* anyTypes, int anyCount) + { + if (anyCount == 0) return true; + + var componentTypes = archetype->Types; + var componentTypesCount = archetype->TypesCount; + for (var i = 0; i < componentTypesCount; i++) + { + var componentTypeIndex = componentTypes[i].TypeIndex; + for (var j = 0; j < anyCount; j++) + { + var anyTypeIndex = anyTypes[j].TypeIndex; + if (componentTypeIndex == anyTypeIndex) return true; + } + } + + return false; + } + + private bool TestMatchingArchetypeNone(Archetype* archetype, ComponentType* noneTypes, int noneCount) + { + var componentTypes = archetype->Types; + var componentTypesCount = archetype->TypesCount; + for (var i = 0; i < componentTypesCount; i++) + { + var componentTypeIndex = componentTypes[i].TypeIndex; + for (var j = 0; j < noneCount; j++) + { + var noneTypeIndex = noneTypes[j].TypeIndex; + if (componentTypeIndex == noneTypeIndex) return false; + } + } + + return true; + } + + private bool TestMatchingArchetypeAll(Archetype* archetype, ComponentType* allTypes, int allCount) + { + var componentTypes = archetype->Types; + var componentTypesCount = archetype->TypesCount; + var foundCount = 0; + for (var i = 0; i < componentTypesCount; i++) + { + var componentTypeIndex = componentTypes[i].TypeIndex; + for (var j = 0; j < allCount; j++) + { + var allTypeIndex = allTypes[j].TypeIndex; + if (componentTypeIndex == allTypeIndex) foundCount++; + } + } + + return foundCount == allCount; + } + + public void AddMatchingArchetypes(ComponentType[] anyComponentTypes, ComponentType[] noneComponentTypes, + ComponentType[] allComponentTypes, NativeList foundArchetypes) + { + var anyCount = anyComponentTypes.Length; + var noneCount = noneComponentTypes.Length; + var allCount = allComponentTypes.Length; + + fixed (ComponentType* any = anyComponentTypes) + { + fixed (ComponentType* none = noneComponentTypes) + { + fixed (ComponentType* all = allComponentTypes) + { + for (var archetype = ArchetypeManager.m_LastArchetype; + archetype != null; + archetype = archetype->PrevArchetype) + { + if (archetype->EntityCount == 0) + continue; + if (!TestMatchingArchetypeAny(archetype, any, anyCount)) + continue; + if (!TestMatchingArchetypeNone(archetype, none, noneCount)) + continue; + if (!TestMatchingArchetypeAll(archetype, all, allCount)) + continue; + + foundArchetypes.Add(new EntityArchetype {Archetype = archetype}); + } + } + } + } + } + + public ArchetypeChunkArray CreateArchetypeChunkArray(NativeList archetypes, + Allocator allocator) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + return new ArchetypeChunkArray(archetypes, allocator, + ComponentJobSafetyManager.GetSafetyHandle(TypeManager.GetTypeIndex(), true)); +#else + return new ArchetypeChunkArray(archetypes, allocator); +#endif + } + + public ArchetypeChunkArray CreateArchetypeChunkArray(ComponentType[] anyComponentTypes, + ComponentType[] noneComponentTypes, ComponentType[] allComponentTypes, Allocator allocator) + { + var foundArchetypes = new NativeList(Allocator.Temp); + AddMatchingArchetypes(anyComponentTypes, noneComponentTypes, allComponentTypes, foundArchetypes); + var chunkStream = CreateArchetypeChunkArray(foundArchetypes, allocator); + foundArchetypes.Dispose(); + return chunkStream; + } + + public ArchetypeChunkComponentType GetArchetypeChunkComponentType(bool isReadOnly) + where T : struct, IComponentData + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + return new ArchetypeChunkComponentType( + ComponentJobSafetyManager.GetSafetyHandle(TypeManager.GetTypeIndex(), isReadOnly), isReadOnly, + GlobalSystemVersion); +#else + return new ArchetypeChunkComponentType(isReadOnly,GlobalSystemVersion); +#endif + } + + public ArchetypeChunkSharedComponentType GetArchetypeChunkSharedComponentType(bool isReadOnly) + where T : struct, ISharedComponentData + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + return new ArchetypeChunkSharedComponentType( + ComponentJobSafetyManager.GetSafetyHandle(TypeManager.GetTypeIndex(), isReadOnly)); +#else + return new ArchetypeChunkSharedComponentType(); +#endif + } + + public ArchetypeChunkEntityType GetArchetypeChunkEntityType(bool isReadOnly) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + return new ArchetypeChunkEntityType( + ComponentJobSafetyManager.GetSafetyHandle(TypeManager.GetTypeIndex(), isReadOnly)); +#else + return new ArchetypeChunkEntityType(); +#endif + } + + internal class EntityManagerDebug + { + private readonly EntityManager m_Manager; + + public EntityManagerDebug(EntityManager entityManager) + { + m_Manager = entityManager; + } + + public void PoisonUnusedDataInAllChunks(EntityArchetype archetype, byte value) + { + for (var c = archetype.Archetype->ChunkList.Begin; c != archetype.Archetype->ChunkList.End; c = c->Next) + ChunkDataUtility.PoisonUnusedChunkData((Chunk*) c, value); + } + + public void SetGlobalSystemVersion(uint version) + { + m_Manager.Entities->GlobalSystemVersion = version; + } + } + } +} diff --git a/Unity.Entities/EntityManager.cs.meta b/Unity.Entities/EntityManager.cs.meta new file mode 100644 index 00000000..9c6e7fc4 --- /dev/null +++ b/Unity.Entities/EntityManager.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: b0543cd94eac04ad1820ac142fba2a98 +timeCreated: 1504638216 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ExclusiveEntityTransaction.cs b/Unity.Entities/ExclusiveEntityTransaction.cs new file mode 100644 index 00000000..8fb6dcfe --- /dev/null +++ b/Unity.Entities/ExclusiveEntityTransaction.cs @@ -0,0 +1,253 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + [NativeContainer] + public unsafe struct ExclusiveEntityTransaction + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private AtomicSafetyHandle m_Safety; +#endif + [NativeDisableUnsafePtrRestriction] private GCHandle m_ArchetypeManager; + + [NativeDisableUnsafePtrRestriction] private GCHandle m_EntityGroupManager; + + [NativeDisableUnsafePtrRestriction] private GCHandle m_SharedComponentDataManager; + + [NativeDisableUnsafePtrRestriction] private EntityDataManager* m_Entities; + + [NativeDisableUnsafePtrRestriction] + private readonly ComponentTypeInArchetype* m_CachedComponentTypeInArchetypeArray; + + internal ExclusiveEntityTransaction(ArchetypeManager archetypes, EntityGroupManager entityGroupManager, + SharedComponentDataManager sharedComponentDataManager, EntityDataManager* data) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_Safety = new AtomicSafetyHandle(); +#endif + m_Entities = data; + m_ArchetypeManager = GCHandle.Alloc(archetypes, GCHandleType.Weak); + m_EntityGroupManager = GCHandle.Alloc(entityGroupManager, GCHandleType.Weak); + m_SharedComponentDataManager = GCHandle.Alloc(sharedComponentDataManager, GCHandleType.Weak); + + m_CachedComponentTypeInArchetypeArray = + (ComponentTypeInArchetype*) UnsafeUtility.Malloc(sizeof(ComponentTypeInArchetype) * 32 * 1024, 16, + Allocator.Persistent); + } + + internal void OnDestroyManager() + { + UnsafeUtility.Free(m_CachedComponentTypeInArchetypeArray, Allocator.Persistent); + m_ArchetypeManager.Free(); + m_EntityGroupManager.Free(); + m_SharedComponentDataManager.Free(); + m_Entities = null; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal void SetAtomicSafetyHandle(AtomicSafetyHandle safety) + { + m_Safety = safety; + } +#endif + + private int PopulatedCachedTypeInArchetypeArray(ComponentType[] requiredComponents) + { + m_CachedComponentTypeInArchetypeArray[0] = new ComponentTypeInArchetype(ComponentType.Create()); + for (var i = 0; i < requiredComponents.Length; ++i) + SortingUtilities.InsertSorted(m_CachedComponentTypeInArchetypeArray, i + 1, requiredComponents[i]); + return requiredComponents.Length + 1; + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + public void CheckAccess() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); +#endif + } + + public EntityArchetype CreateArchetype(params ComponentType[] types) + { + CheckAccess(); + + var archetypeManager = (ArchetypeManager) m_ArchetypeManager.Target; + var groupManager = (EntityGroupManager) m_EntityGroupManager.Target; + + EntityArchetype type; + type.Archetype = archetypeManager.GetOrCreateArchetype(m_CachedComponentTypeInArchetypeArray, + PopulatedCachedTypeInArchetypeArray(types), groupManager); + + return type; + } + + public Entity CreateEntity(EntityArchetype archetype) + { + CheckAccess(); + + Entity entity; + CreateEntityInternal(archetype, &entity, 1); + return entity; + } + + public void CreateEntity(EntityArchetype archetype, NativeArray entities) + { + CreateEntityInternal(archetype, (Entity*) entities.GetUnsafePtr(), entities.Length); + } + + public Entity CreateEntity(params ComponentType[] types) + { + return CreateEntity(CreateArchetype(types)); + } + + private void CreateEntityInternal(EntityArchetype archetype, Entity* entities, int count) + { + CheckAccess(); + var archetypeManager = (ArchetypeManager) m_ArchetypeManager.Target; + m_Entities->CreateEntities(archetypeManager, archetype.Archetype, entities, count); + } + + public Entity Instantiate(Entity srcEntity) + { + Entity entity; + InstantiateInternal(srcEntity, &entity, 1); + return entity; + } + + public void Instantiate(Entity srcEntity, NativeArray outputEntities) + { + InstantiateInternal(srcEntity, (Entity*) outputEntities.GetUnsafePtr(), outputEntities.Length); + } + + private void InstantiateInternal(Entity srcEntity, Entity* outputEntities, int count) + { + CheckAccess(); + + if (!m_Entities->Exists(srcEntity)) + throw new ArgumentException("srcEntity is not a valid entity"); + + var archetypeManager = (ArchetypeManager) m_ArchetypeManager.Target; + var sharedComponentDataManager = (SharedComponentDataManager) m_SharedComponentDataManager.Target; + + m_Entities->InstantiateEntities(archetypeManager, sharedComponentDataManager, srcEntity, outputEntities, + count); + } + + public void DestroyEntity(NativeArray entities) + { + DestroyEntityInternal((Entity*) entities.GetUnsafeReadOnlyPtr(), entities.Length); + } + + public void DestroyEntity(NativeSlice entities) + { + DestroyEntityInternal((Entity*) entities.GetUnsafeReadOnlyPtr(), entities.Length); + } + + public void DestroyEntity(Entity entity) + { + DestroyEntityInternal(&entity, 1); + } + + private void DestroyEntityInternal(Entity* entities, int count) + { + CheckAccess(); + m_Entities->AssertEntitiesExist(entities, count); + + var archetypeManager = (ArchetypeManager) m_ArchetypeManager.Target; + var sharedComponentDataManager = (SharedComponentDataManager) m_SharedComponentDataManager.Target; + var groupManager = (EntityGroupManager) m_EntityGroupManager.Target; + + m_Entities->TryRemoveEntityId(entities, count, archetypeManager, sharedComponentDataManager, groupManager, + m_CachedComponentTypeInArchetypeArray); + } + + public void AddComponent(Entity entity, ComponentType type) + { + CheckAccess(); + + var archetypeManager = (ArchetypeManager) m_ArchetypeManager.Target; + var sharedComponentDataManager = (SharedComponentDataManager) m_SharedComponentDataManager.Target; + var groupManager = (EntityGroupManager) m_EntityGroupManager.Target; + + m_Entities->AssertEntitiesExist(&entity, 1); + m_Entities->AddComponent(entity, type, archetypeManager, sharedComponentDataManager, groupManager, + m_CachedComponentTypeInArchetypeArray); + } + + public void RemoveComponent(Entity entity, ComponentType type) + { + CheckAccess(); + + var archetypeManager = (ArchetypeManager) m_ArchetypeManager.Target; + var sharedComponentDataManager = (SharedComponentDataManager) m_SharedComponentDataManager.Target; + var groupManager = (EntityGroupManager) m_EntityGroupManager.Target; + + m_Entities->AssertEntityHasComponent(entity, type); + m_Entities->RemoveComponent(entity, type, archetypeManager, sharedComponentDataManager, groupManager, + m_CachedComponentTypeInArchetypeArray); + } + + public bool Exists(Entity entity) + { + CheckAccess(); + + return m_Entities->Exists(entity); + } + + public T GetComponentData(Entity entity) where T : struct, IComponentData + { + CheckAccess(); + + var typeIndex = TypeManager.GetTypeIndex(); + m_Entities->AssertEntityHasComponent(entity, typeIndex); + + var ptr = m_Entities->GetComponentDataWithTypeRO(entity, typeIndex); + + T data; + UnsafeUtility.CopyPtrToStructure(ptr, out data); + return data; + } + + public void SetComponentData(Entity entity, T componentData) where T : struct, IComponentData + { + CheckAccess(); + + var typeIndex = TypeManager.GetTypeIndex(); + m_Entities->AssertEntityHasComponent(entity, typeIndex); + + var ptr = m_Entities->GetComponentDataWithTypeRW(entity, typeIndex, m_Entities->GlobalSystemVersion); + UnsafeUtility.CopyStructureToPtr(ref componentData, ptr); + } + + public T GetSharedComponentData(Entity entity) where T : struct, ISharedComponentData + { + var typeIndex = TypeManager.GetTypeIndex(); + m_Entities->AssertEntityHasComponent(entity, typeIndex); + + var sharedComponentDataManager = (SharedComponentDataManager) m_SharedComponentDataManager.Target; + + var sharedComponentIndex = m_Entities->GetSharedComponentDataIndex(entity, typeIndex); + return sharedComponentDataManager.GetSharedComponentData(sharedComponentIndex); + } + + public void SetSharedComponentData(Entity entity, T componentData) where T : struct, ISharedComponentData + { + CheckAccess(); + + var typeIndex = TypeManager.GetTypeIndex(); + m_Entities->AssertEntityHasComponent(entity, typeIndex); + + var archetypeManager = (ArchetypeManager) m_ArchetypeManager.Target; + var sharedComponentDataManager = (SharedComponentDataManager) m_SharedComponentDataManager.Target; + + var newSharedComponentDataIndex = sharedComponentDataManager.InsertSharedComponent(componentData); + m_Entities->SetSharedComponentDataIndex(archetypeManager, sharedComponentDataManager, entity, typeIndex, + newSharedComponentDataIndex); + sharedComponentDataManager.RemoveReference(newSharedComponentDataIndex); + } + } +} diff --git a/Unity.Entities/ExclusiveEntityTransaction.cs.meta b/Unity.Entities/ExclusiveEntityTransaction.cs.meta new file mode 100644 index 00000000..593f95b3 --- /dev/null +++ b/Unity.Entities/ExclusiveEntityTransaction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bcbab3df70ff24c658cb5a42dd316af3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/HashUtility.cs b/Unity.Entities/HashUtility.cs new file mode 100644 index 00000000..dc7c8cc3 --- /dev/null +++ b/Unity.Entities/HashUtility.cs @@ -0,0 +1,32 @@ +namespace Unity.Entities +{ + internal static unsafe class HashUtility + { + public static uint Fletcher32(ushort* data, int count) + { + unchecked + { + uint sum1 = 0xff; + uint sum2 = 0xff; + while (count > 0) + { + var batchCount = count < 359 ? count : 359; + for (var i = 0; i < batchCount; ++i) + { + sum1 += data[i]; + sum2 += sum1; + } + + sum1 = (sum1 & 0xffff) + (sum1 >> 16); + sum2 = (sum2 & 0xffff) + (sum2 >> 16); + count -= batchCount; + data += batchCount; + } + + sum1 = (sum1 & 0xffff) | (sum1 >> 16); + sum2 = (sum2 & 0xffff) | (sum2 >> 16); + return (sum2 << 16) | sum1; + } + } + } +} diff --git a/Unity.Entities/HashUtility.cs.meta b/Unity.Entities/HashUtility.cs.meta new file mode 100644 index 00000000..2c6aa875 --- /dev/null +++ b/Unity.Entities/HashUtility.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: e29179cfef2ee43d7bab03b726e2bab2 +timeCreated: 1504726216 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/IComponentData.cs b/Unity.Entities/IComponentData.cs new file mode 100644 index 00000000..05e11893 --- /dev/null +++ b/Unity.Entities/IComponentData.cs @@ -0,0 +1,14 @@ +namespace Unity.Entities +{ + public interface IComponentData + { + } + + public interface ISharedComponentData + { + } + + public interface ISystemStateComponentData : IComponentData + { + } +} diff --git a/Unity.Entities/IComponentData.cs.meta b/Unity.Entities/IComponentData.cs.meta new file mode 100644 index 00000000..195bc82d --- /dev/null +++ b/Unity.Entities/IComponentData.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 312296ae7396a40e4912ab93ea6e4697 +timeCreated: 1504685842 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/IJobProcessComponentData.cs b/Unity.Entities/IJobProcessComponentData.cs new file mode 100644 index 00000000..b373a015 --- /dev/null +++ b/Unity.Entities/IJobProcessComponentData.cs @@ -0,0 +1,886 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.InteropServices; +using Unity.Assertions; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using UnityEngine.Scripting; +using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; + +namespace Unity.Entities +{ + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field)] + public class ChangedFilterAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Struct)] + public class RequireComponentTagAttribute : Attribute + { + public Type[] TagComponents; + + public RequireComponentTagAttribute(params Type[] tagComponents) + { + TagComponents = tagComponents; + } + } + + [AttributeUsage(AttributeTargets.Struct)] + public class RequireSubtractiveComponentAttribute : Attribute + { + public Type[] SubtractiveComponents; + + public RequireSubtractiveComponentAttribute(params Type[] subtractiveComponents) + { + SubtractiveComponents = subtractiveComponents; + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IBaseJobProcessComponentData + { + } + + //@TODO: It would be nice to get rid of these interfaces completely. + //Right now implementation needs it, but they pollute public API in annoying ways. + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IBaseJobProcessComponentData_3 : IBaseJobProcessComponentData + { + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IBaseJobProcessComponentData_2 : IBaseJobProcessComponentData + { + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IBaseJobProcessComponentData_1 : IBaseJobProcessComponentData + { + } + + [JobProducerType(typeof(JobProcessComponentDataExtensions.JobStruct_Process1<,>))] + public interface IJobProcessComponentData : IBaseJobProcessComponentData_1 + where T0 : struct, IComponentData + { + void Execute(ref T0 data); + } + + [JobProducerType(typeof(JobProcessComponentDataExtensions.JobStruct_Process2<,,>))] + public interface IJobProcessComponentData : IBaseJobProcessComponentData_2 + where T0 : struct, IComponentData + where T1 : struct, IComponentData + { + void Execute(ref T0 data0, ref T1 data1); + } + + [JobProducerType(typeof(JobProcessComponentDataExtensions.JobStruct_Process3<,,,>))] + public interface IJobProcessComponentData : IBaseJobProcessComponentData_3 + where T0 : struct, IComponentData + where T1 : struct, IComponentData + where T2 : struct, IComponentData + { + void Execute(ref T0 data0, ref T1 data1, ref T2 data2); + } + + internal struct JobProcessComponentDataCache + { + public IntPtr JobReflectionData; + public IntPtr JobReflectionDataParallelFor; + public ComponentType[] Types; + public ComponentType[] FilterChanged; + + public int ProcessTypesCount; + + public ComponentGroup ComponentGroup; + public ComponentSystemBase ComponentSystem; + } + + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + [StructLayout(LayoutKind.Sequential)] + internal struct ProcessIterationData + { + public ComponentChunkIterator Iterator0; + public ComponentChunkIterator Iterator1; + public ComponentChunkIterator Iterator2; + + public int IsReadOnly0; + public int IsReadOnly1; + public int IsReadOnly2; + + public int IsChangedFilter0; + public int IsChangedFilter1; + public int IsChangedFilter2; + + public bool m_IsParallelFor; + + public int m_Length; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + + public int m_MinIndex; + public int m_MaxIndex; + +#pragma warning disable 414 + public int m_SafetyReadOnlyCount; + public int m_SafetyReadWriteCount; + public AtomicSafetyHandle m_Safety0; + public AtomicSafetyHandle m_Safety1; + public AtomicSafetyHandle m_Safety2; +#pragma warning restore +#endif + } + + internal static class IJobProcessComponentDataUtility + { + public static ComponentType[] GetComponentTypes(Type jobType) + { + var interfaceType = GetIJobProcessComponentDataInterface(jobType); + if (interfaceType != null) + { + int temp; + ComponentType[] temp2; + return GetComponentTypes(jobType, interfaceType, out temp, out temp2); + } + + return null; + } + + private static ComponentType[] GetComponentTypes(Type jobType, Type interfaceType, out int processCount, + out ComponentType[] changedFilter) + { + var genericArgs = interfaceType.GetGenericArguments(); + + var executeMethodParameters = jobType.GetMethod("Execute").GetParameters(); + + var componentTypes = new List(); + var changedFilterTypes = new List(); + + for (var i = 0; i < genericArgs.Length; i++) + { + var isReadonly = executeMethodParameters[i].GetCustomAttribute(typeof(ReadOnlyAttribute)) != null; + var type = new ComponentType(genericArgs[i], + isReadonly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite); + componentTypes.Add(type); + + var isChangedFilter = executeMethodParameters[i].GetCustomAttribute(typeof(ChangedFilterAttribute)) != + null; + if (isChangedFilter) + changedFilterTypes.Add(type); + } + + var subtractive = jobType.GetCustomAttribute(); + if (subtractive != null) + foreach (var type in subtractive.SubtractiveComponents) + componentTypes.Add(ComponentType.Subtractive(type)); + + var requiredTags = jobType.GetCustomAttribute(); + if (requiredTags != null) + foreach (var type in requiredTags.TagComponents) + componentTypes.Add(ComponentType.ReadOnly(type)); + + processCount = genericArgs.Length; + changedFilter = changedFilterTypes.ToArray(); + return componentTypes.ToArray(); + } + + private static IntPtr GetJobReflection(Type jobType, Type wrapperJobType, Type interfaceType, + bool isIJobParallelFor) + { + Assert.AreNotEqual(null, wrapperJobType); + Assert.AreNotEqual(null, interfaceType); + + var genericArgs = interfaceType.GetGenericArguments(); + + var jobTypeAndGenericArgs = new List(); + jobTypeAndGenericArgs.Add(jobType); + jobTypeAndGenericArgs.AddRange(genericArgs); + var resolvedWrapperJobType = wrapperJobType.MakeGenericType(jobTypeAndGenericArgs.ToArray()); + + object[] parameters = {isIJobParallelFor ? JobType.ParallelFor : JobType.Single}; + var reflectionDataRes = resolvedWrapperJobType.GetMethod("Initialize").Invoke(null, parameters); + return (IntPtr) reflectionDataRes; + } + + private static Type GetIJobProcessComponentDataInterface(Type jobType) + { + foreach (var iType in jobType.GetInterfaces()) + if (iType.Assembly == typeof(IBaseJobProcessComponentData).Assembly && + iType.Name.StartsWith("IJobProcessComponentData")) + return iType; + + return null; + } + + internal static unsafe void Initialize(ComponentSystemBase system, Type jobType, Type wrapperJobType, + bool isParallelFor, ref JobProcessComponentDataCache cache, out ProcessIterationData iterator) + { + if (isParallelFor && cache.JobReflectionDataParallelFor == IntPtr.Zero || + !isParallelFor && cache.JobReflectionData == IntPtr.Zero) + { + var iType = GetIJobProcessComponentDataInterface(jobType); + if (cache.Types == null) + cache.Types = GetComponentTypes(jobType, iType, out cache.ProcessTypesCount, + out cache.FilterChanged); + + var res = GetJobReflection(jobType, wrapperJobType, iType, isParallelFor); + + if (isParallelFor) + cache.JobReflectionDataParallelFor = res; + else + cache.JobReflectionData = res; + } + + if (cache.ComponentSystem != system) + { + cache.ComponentGroup = system.GetComponentGroupInternal(cache.Types); + if (cache.FilterChanged.Length != 0) + cache.ComponentGroup.SetFilterChanged(cache.FilterChanged); + else + cache.ComponentGroup.ResetFilter(); + + cache.ComponentSystem = system; + } + + var group = cache.ComponentGroup; + + // Readonly + iterator.IsReadOnly0 = iterator.IsReadOnly1 = iterator.IsReadOnly2 = 0; + fixed (int* isReadOnly = &iterator.IsReadOnly0) + { + for (var i = 0; i != cache.ProcessTypesCount; i++) + isReadOnly[i] = cache.Types[i].AccessModeType == ComponentType.AccessMode.ReadOnly ? 1 : 0; + } + + // Iterator & length + iterator.Iterator0 = default(ComponentChunkIterator); + iterator.Iterator1 = default(ComponentChunkIterator); + iterator.Iterator2 = default(ComponentChunkIterator); + var length = -1; + fixed (ComponentChunkIterator* iterators = &iterator.Iterator0) + { + for (var i = 0; i != cache.ProcessTypesCount; i++) + { + group.GetComponentChunkIterator(out length, out iterators[i]); + iterators[i].IndexInComponentGroup = group.GetIndexInComponentGroup(cache.Types[i].TypeIndex); + } + } + + iterator.IsChangedFilter0 = 0; + iterator.IsChangedFilter1 = 0; + iterator.IsChangedFilter2 = 0; + + fixed (ComponentChunkIterator* iterators = &iterator.Iterator0) + fixed (int* isChangedFilters = &iterator.IsChangedFilter0) + { + foreach (var type in cache.FilterChanged) + { + var componentIndexInGroup = group.GetIndexInComponentGroup(type.TypeIndex); + + for (var iteratorIndex = 0; iteratorIndex < 3; ++iteratorIndex) + if (componentIndexInGroup == iterators[iteratorIndex].IndexInComponentGroup) + isChangedFilters[iteratorIndex] = 1; + } + } + + iterator.m_IsParallelFor = isParallelFor; + iterator.m_Length = cache.FilterChanged.Length > 0 + ? group.CalculateNumberOfChunksWithoutFiltering() + : length; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + iterator.m_MaxIndex = length - 1; + iterator.m_MinIndex = 0; + + // Safety + iterator.m_Safety0 = iterator.m_Safety1 = iterator.m_Safety2 = default(AtomicSafetyHandle); + + iterator.m_SafetyReadOnlyCount = 0; + fixed (AtomicSafetyHandle* safety = &iterator.m_Safety0) + { + for (var i = 0; i != cache.ProcessTypesCount; i++) + if (cache.Types[i].AccessModeType == ComponentType.AccessMode.ReadOnly) + { + safety[iterator.m_SafetyReadOnlyCount] = + group.GetSafetyHandle(group.GetIndexInComponentGroup(cache.Types[i].TypeIndex)); + iterator.m_SafetyReadOnlyCount++; + } + } + + iterator.m_SafetyReadWriteCount = 0; + fixed (AtomicSafetyHandle* safety = &iterator.m_Safety0) + { + for (var i = 0; i != cache.ProcessTypesCount; i++) + if (cache.Types[i].AccessModeType == ComponentType.AccessMode.ReadWrite) + { + safety[iterator.m_SafetyReadOnlyCount + iterator.m_SafetyReadWriteCount] = + group.GetSafetyHandle(group.GetIndexInComponentGroup(cache.Types[i].TypeIndex)); + iterator.m_SafetyReadWriteCount++; + } + } + + Assert.AreEqual(cache.ProcessTypesCount, iterator.m_SafetyReadWriteCount + iterator.m_SafetyReadOnlyCount); +#endif + } + } + + public static class JobProcessComponentDataExtensions + { + //NOTE: It would be much better if C# could resolve the branch with generic resolving, + // but apparently the interface constraint is not enough.. + + public static JobHandle Schedule(this T jobData, ComponentSystemBase system, int innerloopBatchCount, + JobHandle dependsOn = default(JobHandle)) + where T : struct, IBaseJobProcessComponentData + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (innerloopBatchCount <= 0) + throw new ArgumentException($"innerloopBatchCount must be larger than 0."); +#endif + + var typeT = typeof(T); + if (typeof(IBaseJobProcessComponentData_1).IsAssignableFrom(typeT)) + return ScheduleInternal_1(ref jobData, system, innerloopBatchCount, dependsOn, ScheduleMode.Batched); + if (typeof(IBaseJobProcessComponentData_2).IsAssignableFrom(typeT)) + return ScheduleInternal_2(ref jobData, system, innerloopBatchCount, dependsOn, ScheduleMode.Batched); + return ScheduleInternal_3(ref jobData, system, innerloopBatchCount, dependsOn, ScheduleMode.Batched); + } + + public static JobHandle Schedule(this T jobData, ComponentSystemBase system, + JobHandle dependsOn = default(JobHandle)) + where T : struct, IBaseJobProcessComponentData + { + var typeT = typeof(T); + if (typeof(IBaseJobProcessComponentData_1).IsAssignableFrom(typeT)) + return ScheduleInternal_1(ref jobData, system, -1, dependsOn, ScheduleMode.Batched); + if (typeof(IBaseJobProcessComponentData_2).IsAssignableFrom(typeT)) + return ScheduleInternal_2(ref jobData, system, -1, dependsOn, ScheduleMode.Batched); + return ScheduleInternal_3(ref jobData, system, -1, dependsOn, ScheduleMode.Batched); + } + + public static void Run(this T jobData, ComponentSystemBase system) + where T : struct, IBaseJobProcessComponentData + { + var typeT = typeof(T); + if (typeof(IBaseJobProcessComponentData_1).IsAssignableFrom(typeT)) + ScheduleInternal_1(ref jobData, system, -1, default(JobHandle), ScheduleMode.Run); + else if (typeof(IBaseJobProcessComponentData_2).IsAssignableFrom(typeT)) + ScheduleInternal_2(ref jobData, system, -1, default(JobHandle), ScheduleMode.Run); + else + ScheduleInternal_3(ref jobData, system, -1, default(JobHandle), ScheduleMode.Run); + } + + private static unsafe JobHandle Schedule(void* fullData, int length, int innerloopBatchCount, + bool isParallelFor, ref JobProcessComponentDataCache cache, JobHandle dependsOn, ScheduleMode mode) + { + if (isParallelFor) + { + var scheduleParams = + new JobsUtility.JobScheduleParameters(fullData, cache.JobReflectionDataParallelFor, dependsOn, + mode); + return JobsUtility.ScheduleParallelFor(ref scheduleParams, length, innerloopBatchCount); + } + else + { + var scheduleParams = + new JobsUtility.JobScheduleParameters(fullData, cache.JobReflectionData, dependsOn, mode); + return JobsUtility.Schedule(ref scheduleParams); + } + } + + internal static unsafe JobHandle ScheduleInternal_1(ref T jobData, ComponentSystemBase system, + int innerloopBatchCount, + JobHandle dependsOn, ScheduleMode mode) + where T : struct + { + JobStruct_ProcessInfer_1 fullData; + fullData.Data = jobData; + + var isParallelFor = innerloopBatchCount != -1; + IJobProcessComponentDataUtility.Initialize(system, typeof(T), typeof(JobStruct_Process1<,>), isParallelFor, + ref JobStruct_ProcessInfer_1.Cache, out fullData.Iterator); + return Schedule(UnsafeUtility.AddressOf(ref fullData), fullData.Iterator.m_Length, innerloopBatchCount, + isParallelFor, ref JobStruct_ProcessInfer_1.Cache, dependsOn, mode); + } + + internal static unsafe JobHandle ScheduleInternal_2(ref T jobData, ComponentSystemBase system, + int innerloopBatchCount, JobHandle dependsOn, ScheduleMode mode) + where T : struct + { + JobStruct_ProcessInfer_2 fullData; + fullData.Data = jobData; + + var isParallelFor = innerloopBatchCount != -1; + IJobProcessComponentDataUtility.Initialize(system, typeof(T), typeof(JobStruct_Process2<,,>), isParallelFor, + ref JobStruct_ProcessInfer_2.Cache, out fullData.Iterator); + return Schedule(UnsafeUtility.AddressOf(ref fullData), fullData.Iterator.m_Length, innerloopBatchCount, + isParallelFor, ref JobStruct_ProcessInfer_2.Cache, dependsOn, mode); + } + + internal static unsafe JobHandle ScheduleInternal_3(ref T jobData, ComponentSystemBase system, + int innerloopBatchCount, JobHandle dependsOn, ScheduleMode mode) + where T : struct + { + JobStruct_ProcessInfer_3 fullData; + fullData.Data = jobData; + + var isParallelFor = innerloopBatchCount != -1; + IJobProcessComponentDataUtility.Initialize(system, typeof(T), typeof(JobStruct_Process3<,,,>), + isParallelFor, ref JobStruct_ProcessInfer_3.Cache, out fullData.Iterator); + return Schedule(UnsafeUtility.AddressOf(ref fullData), fullData.Iterator.m_Length, innerloopBatchCount, + isParallelFor, ref JobStruct_ProcessInfer_3.Cache, dependsOn, mode); + } + + [StructLayout(LayoutKind.Sequential)] + private struct JobStruct_ProcessInfer_1 where T : struct + { + public static JobProcessComponentDataCache Cache; + + public ProcessIterationData Iterator; + public T Data; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct JobStruct_Process1 + where T : struct, IJobProcessComponentData + where U0 : struct, IComponentData + { + public ProcessIterationData Iterator; + public T Data; + + [Preserve] + public static IntPtr Initialize(JobType jobType) + { + return JobsUtility.CreateJobReflectionData(typeof(JobStruct_Process1), typeof(T), jobType, + (ExecuteJobFunction) Execute); + } + + private delegate void ExecuteJobFunction(ref JobStruct_Process1 data, IntPtr additionalPtr, + IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex); + + private static unsafe void ExecuteInnerLoop(ref JobStruct_Process1 jobData, int begin, int end) + { + ComponentChunkCache cache0; + + while (begin != end) + { + jobData.Iterator.Iterator0.UpdateCache(begin, out cache0, jobData.Iterator.IsReadOnly0 == 0); + + var ptr0 = UnsafeUtilityEx.RestrictNoAlias(cache0.CachedPtr); + + var curEnd = Math.Min(end, cache0.CachedEndIndex); + + for (var i = begin; i != curEnd; i++) + { +#if CSHARP_7_OR_LATER + ref var value0 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr0, i); + jobData.Data.Execute(ref value0); +#else + var value0 = UnsafeUtility.ReadArrayElement(ptr0, i); + + jobData.Data.Execute(ref value0); + + if (jobData.Iterator.IsReadOnly0 == 0) + UnsafeUtility.WriteArrayElement(ptr0, i, value0); +#endif + } + + begin = curEnd; + } + } + + private static unsafe void ExecuteInnerLoopByChunk(ref JobStruct_Process1 jobData, int begin, + int end) + { + ComponentChunkCache cache0; + + for (var blockIndex = begin; blockIndex != end; ++blockIndex) + { + jobData.Iterator.Iterator0.MoveToChunkByIndex(blockIndex); + + var processBlock = false; + + processBlock |= jobData.Iterator.IsChangedFilter0 != 0 && + jobData.Iterator.Iterator0.IsCurrentChunkChanged(); + + if (!processBlock) + continue; + + jobData.Iterator.Iterator0.UpdateCacheToCurrentChunk(out cache0, jobData.Iterator.IsReadOnly0 == 0); + var ptr0 = UnsafeUtilityEx.RestrictNoAlias(cache0.CachedPtr); + + for (var i = cache0.CachedBeginIndex; i != cache0.CachedEndIndex; i++) + { +#if CSHARP_7_OR_LATER + ref var value0 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr0, i); + jobData.Data.Execute(ref value0); +#else + var value0 = UnsafeUtility.ReadArrayElement(ptr0, i); + + jobData.Data.Execute(ref value0); + + if (jobData.Iterator.IsReadOnly0 == 0) + UnsafeUtility.WriteArrayElement(ptr0, i, value0); +#endif + } + } + } + + public static unsafe void Execute(ref JobStruct_Process1 jobData, IntPtr additionalPtr, + IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex) + { + if (jobData.Iterator.IsChangedFilter0 != 0) + { + int begin; + int end; + while (JobsUtility.GetWorkStealingRange(ref ranges, jobIndex, out begin, out end)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + JobsUtility.PatchBufferMinMaxRanges(bufferRangePatchData, UnsafeUtility.AddressOf(ref jobData), + begin, end - begin); +#endif + ExecuteInnerLoopByChunk(ref jobData, begin, end); + } + } + else if (jobData.Iterator.m_IsParallelFor) + { + int begin; + int end; + while (JobsUtility.GetWorkStealingRange(ref ranges, jobIndex, out begin, out end)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + JobsUtility.PatchBufferMinMaxRanges(bufferRangePatchData, UnsafeUtility.AddressOf(ref jobData), + begin, end - begin); +#endif + ExecuteInnerLoop(ref jobData, begin, end); + } + } + else + { + ExecuteInnerLoop(ref jobData, 0, jobData.Iterator.m_Length); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct JobStruct_ProcessInfer_2 where T : struct + { + public static JobProcessComponentDataCache Cache; + + public ProcessIterationData Iterator; + public T Data; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct JobStruct_Process2 + where T : struct, IJobProcessComponentData + where U0 : struct, IComponentData + where U1 : struct, IComponentData + { + public ProcessIterationData Iterator; + public T Data; + + [Preserve] + public static IntPtr Initialize(JobType jobType) + { + return JobsUtility.CreateJobReflectionData(typeof(JobStruct_Process2), typeof(T), jobType, + (ExecuteJobFunction) Execute); + } + + private delegate void ExecuteJobFunction(ref JobStruct_Process2 data, IntPtr additionalPtr, + IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex); + + + public static unsafe void ExecuteInnerLoop(ref JobStruct_Process2 jobData, int begin, int end) + { + ComponentChunkCache cache0, cache1; + + while (begin != end) + { + jobData.Iterator.Iterator0.UpdateCache(begin, out cache0, jobData.Iterator.IsReadOnly0 == 0); + var ptr0 = UnsafeUtilityEx.RestrictNoAlias(cache0.CachedPtr); + + jobData.Iterator.Iterator1.UpdateCache(begin, out cache1, jobData.Iterator.IsReadOnly1 == 0); + var ptr1 = UnsafeUtilityEx.RestrictNoAlias(cache1.CachedPtr); + + var curEnd = Math.Min(end, cache0.CachedEndIndex); + + for (var i = begin; i != curEnd; i++) + { +#if CSHARP_7_OR_LATER + ref var value0 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr0, i); + ref var value1 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr1, i); + jobData.Data.Execute(ref value0, ref value1); +#else + var value0 = UnsafeUtility.ReadArrayElement(ptr0, i); + var value1 = UnsafeUtility.ReadArrayElement(ptr1, i); + + jobData.Data.Execute(ref value0, ref value1); + + if (jobData.Iterator.IsReadOnly0 == 0) + UnsafeUtility.WriteArrayElement(ptr0, i, value0); + if (jobData.Iterator.IsReadOnly1 == 0) + UnsafeUtility.WriteArrayElement(ptr1, i, value1); + +#endif + } + + begin = curEnd; + } + } + + private static unsafe void ExecuteInnerLoopByChunk(ref JobStruct_Process2 jobData, int begin, + int end) + { + ComponentChunkCache cache0, cache1; + + for (var blockIndex = begin; blockIndex != end; ++blockIndex) + { + jobData.Iterator.Iterator0.MoveToChunkByIndex(blockIndex); + jobData.Iterator.Iterator1.MoveToChunkByIndex(blockIndex); + + var processBlock = false; + + processBlock |= jobData.Iterator.IsChangedFilter0 != 0 && + jobData.Iterator.Iterator0.IsCurrentChunkChanged(); + processBlock |= jobData.Iterator.IsChangedFilter1 != 0 && + jobData.Iterator.Iterator1.IsCurrentChunkChanged(); + + if (!processBlock) + continue; + + jobData.Iterator.Iterator0.UpdateCacheToCurrentChunk(out cache0, jobData.Iterator.IsReadOnly0 == 0); + var ptr0 = UnsafeUtilityEx.RestrictNoAlias(cache0.CachedPtr); + + jobData.Iterator.Iterator1.UpdateCacheToCurrentChunk(out cache1, jobData.Iterator.IsReadOnly1 == 0); + var ptr1 = UnsafeUtilityEx.RestrictNoAlias(cache1.CachedPtr); + + for (var i = cache0.CachedBeginIndex; i != cache0.CachedEndIndex; i++) + { +#if CSHARP_7_OR_LATER + ref var value0 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr0, i); + ref var value1 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr1, i); + jobData.Data.Execute(ref value0, ref value1); +#else + var value0 = UnsafeUtility.ReadArrayElement(ptr0, i); + var value1 = UnsafeUtility.ReadArrayElement(ptr1, i); + + jobData.Data.Execute(ref value0, ref value1); + + if (jobData.Iterator.IsReadOnly0 == 0) + UnsafeUtility.WriteArrayElement(ptr0, i, value0); + if (jobData.Iterator.IsReadOnly1 == 0) + UnsafeUtility.WriteArrayElement(ptr1, i, value1); +#endif + } + } + } + + public static unsafe void Execute(ref JobStruct_Process2 jobData, IntPtr additionalPtr, + IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex) + { + if (jobData.Iterator.IsChangedFilter0 != 0 || jobData.Iterator.IsChangedFilter1 != 0) + { + int begin; + int end; + while (JobsUtility.GetWorkStealingRange(ref ranges, jobIndex, out begin, out end)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + JobsUtility.PatchBufferMinMaxRanges(bufferRangePatchData, UnsafeUtility.AddressOf(ref jobData), + begin, end - begin); +#endif + ExecuteInnerLoopByChunk(ref jobData, begin, end); + } + } + else if (jobData.Iterator.m_IsParallelFor) + { + int begin; + int end; + while (JobsUtility.GetWorkStealingRange(ref ranges, jobIndex, out begin, out end)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + JobsUtility.PatchBufferMinMaxRanges(bufferRangePatchData, UnsafeUtility.AddressOf(ref jobData), + begin, end - begin); +#endif + ExecuteInnerLoop(ref jobData, begin, end); + } + } + else + { + ExecuteInnerLoop(ref jobData, 0, jobData.Iterator.m_Length); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct JobStruct_ProcessInfer_3 where T : struct + { + public static JobProcessComponentDataCache Cache; + + public ProcessIterationData Iterator; + public T Data; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct JobStruct_Process3 + where T : struct, IJobProcessComponentData + where U0 : struct, IComponentData + where U1 : struct, IComponentData + where U2 : struct, IComponentData + { + public ProcessIterationData Iterator; + public T Data; + + [Preserve] + public static IntPtr Initialize(JobType jobType) + { + return JobsUtility.CreateJobReflectionData(typeof(JobStruct_Process3), typeof(T), + jobType, (ExecuteJobFunction) Execute); + } + + private delegate void ExecuteJobFunction(ref JobStruct_Process3 data, IntPtr additionalPtr, + IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex); + + private static unsafe void ExecuteInnerLoop(ref JobStruct_Process3 jobData, int begin, + int end) + { + ComponentChunkCache cache0, cache1, cache2; + + while (begin != end) + { + jobData.Iterator.Iterator0.UpdateCache(begin, out cache0, jobData.Iterator.IsReadOnly0 == 0); + var ptr0 = UnsafeUtilityEx.RestrictNoAlias(cache0.CachedPtr); + + jobData.Iterator.Iterator1.UpdateCache(begin, out cache1, jobData.Iterator.IsReadOnly1 == 0); + var ptr1 = UnsafeUtilityEx.RestrictNoAlias(cache1.CachedPtr); + + jobData.Iterator.Iterator2.UpdateCache(begin, out cache2, jobData.Iterator.IsReadOnly2 == 0); + var ptr2 = UnsafeUtilityEx.RestrictNoAlias(cache2.CachedPtr); + + var curEnd = Math.Min(end, cache0.CachedEndIndex); + + for (var i = begin; i != curEnd; i++) + { +#if CSHARP_7_OR_LATER + ref var value0 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr0, i); + ref var value1 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr1, i); + ref var value2 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr2, i); + jobData.Data.Execute(ref value0, ref value1, ref value2); +#else + var value0 = UnsafeUtility.ReadArrayElement(ptr0, i); + var value1 = UnsafeUtility.ReadArrayElement(ptr1, i); + var value2 = UnsafeUtility.ReadArrayElement(ptr2, i); + + jobData.Data.Execute(ref value0, ref value1, ref value2); + + if (jobData.Iterator.IsReadOnly0 == 0) + UnsafeUtility.WriteArrayElement(ptr0, i, value0); + if (jobData.Iterator.IsReadOnly1 == 0) + UnsafeUtility.WriteArrayElement(ptr1, i, value1); + if (jobData.Iterator.IsReadOnly2 == 0) + UnsafeUtility.WriteArrayElement(ptr2, i, value2); +#endif + } + + begin = curEnd; + } + } + + private static unsafe void ExecuteInnerLoopByChunk(ref JobStruct_Process3 jobData, int begin, + int end) + { + ComponentChunkCache cache0, cache1, cache2; + + for (var blockIndex = begin; blockIndex != end; ++blockIndex) + { + jobData.Iterator.Iterator0.MoveToChunkByIndex(blockIndex); + jobData.Iterator.Iterator1.MoveToChunkByIndex(blockIndex); + jobData.Iterator.Iterator2.MoveToChunkByIndex(blockIndex); + + var processBlock = false; + + processBlock |= jobData.Iterator.IsChangedFilter0 != 0 && + jobData.Iterator.Iterator0.IsCurrentChunkChanged(); + processBlock |= jobData.Iterator.IsChangedFilter1 != 0 && + jobData.Iterator.Iterator1.IsCurrentChunkChanged(); + processBlock |= jobData.Iterator.IsChangedFilter2 != 0 && + jobData.Iterator.Iterator2.IsCurrentChunkChanged(); + + if (!processBlock) + continue; + + jobData.Iterator.Iterator0.UpdateCacheToCurrentChunk(out cache0, jobData.Iterator.IsReadOnly0 == 0); + var ptr0 = UnsafeUtilityEx.RestrictNoAlias(cache0.CachedPtr); + + jobData.Iterator.Iterator1.UpdateCacheToCurrentChunk(out cache1, jobData.Iterator.IsReadOnly1 == 0); + var ptr1 = UnsafeUtilityEx.RestrictNoAlias(cache1.CachedPtr); + + jobData.Iterator.Iterator2.UpdateCacheToCurrentChunk(out cache2, jobData.Iterator.IsReadOnly2 == 0); + var ptr2 = UnsafeUtilityEx.RestrictNoAlias(cache2.CachedPtr); + + for (var i = cache0.CachedBeginIndex; i != cache0.CachedEndIndex; i++) + { +#if CSHARP_7_OR_LATER + ref var value0 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr0, i); + ref var value1 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr1, i); + ref var value2 = ref UnsafeUtilityEx.ArrayElementAsRef(ptr2, i); + jobData.Data.Execute(ref value0, ref value1, ref value2); +#else + var value0 = UnsafeUtility.ReadArrayElement(ptr0, i); + var value1 = UnsafeUtility.ReadArrayElement(ptr1, i); + var value2 = UnsafeUtility.ReadArrayElement(ptr2, i); + + jobData.Data.Execute(ref value0, ref value1, ref value2); + + if (jobData.Iterator.IsReadOnly0 == 0) + UnsafeUtility.WriteArrayElement(ptr0, i, value0); + if (jobData.Iterator.IsReadOnly1 == 0) + UnsafeUtility.WriteArrayElement(ptr1, i, value1); + if (jobData.Iterator.IsReadOnly2 == 0) + UnsafeUtility.WriteArrayElement(ptr2, i, value2); +#endif + } + } + } + + public static unsafe void Execute(ref JobStruct_Process3 jobData, IntPtr additionalPtr, + IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex) + { + if (jobData.Iterator.IsChangedFilter0 != 0 || jobData.Iterator.IsChangedFilter1 != 0 || + jobData.Iterator.IsChangedFilter2 != 0) + { + int begin; + int end; + while (JobsUtility.GetWorkStealingRange(ref ranges, jobIndex, out begin, out end)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + JobsUtility.PatchBufferMinMaxRanges(bufferRangePatchData, UnsafeUtility.AddressOf(ref jobData), + begin, end - begin); +#endif + ExecuteInnerLoopByChunk(ref jobData, begin, end); + } + } + else if (jobData.Iterator.m_IsParallelFor) + { + int begin; + int end; + while (JobsUtility.GetWorkStealingRange(ref ranges, jobIndex, out begin, out end)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + JobsUtility.PatchBufferMinMaxRanges(bufferRangePatchData, UnsafeUtility.AddressOf(ref jobData), + begin, end - begin); +#endif + ExecuteInnerLoop(ref jobData, begin, end); + } + } + else + { + ExecuteInnerLoop(ref jobData, 0, jobData.Iterator.m_Length); + } + } + } + } +} diff --git a/Unity.Entities/IJobProcessComponentData.cs.meta b/Unity.Entities/IJobProcessComponentData.cs.meta new file mode 100644 index 00000000..38051e67 --- /dev/null +++ b/Unity.Entities/IJobProcessComponentData.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 9d0a770fc8003446c91c88f48fa635f0 +timeCreated: 1505652857 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Injection.meta b/Unity.Entities/Injection.meta new file mode 100644 index 00000000..0b6e859b --- /dev/null +++ b/Unity.Entities/Injection.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8bf694bfa86dd41fa8c549c11468bc71 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Injection/ComponentSystemInjection.cs b/Unity.Entities/Injection/ComponentSystemInjection.cs new file mode 100644 index 00000000..1af1b14a --- /dev/null +++ b/Unity.Entities/Injection/ComponentSystemInjection.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Unity.Entities +{ + internal static class ComponentSystemInjection + { + public static string GetFieldString(FieldInfo info) + { + return $"{info.DeclaringType.Name}.{info.Name}"; + } + + public static void Inject(ComponentSystemBase componentSystem, World world, EntityManager entityManager, + out InjectComponentGroupData[] outInjectGroups, out InjectFromEntityData outInjectFromEntityData) + { + var componentSystemType = componentSystem.GetType(); + + ValidateNoStaticInjectDependencies(componentSystemType); + + var fields = + componentSystemType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + var injectGroups = new List(); + + var injectFromEntity = new List(); + var injectFromFixedArray = new List(); + + foreach (var field in fields) + { + var attr = field.GetCustomAttributes(typeof(InjectAttribute), true); + if (attr.Length == 0) + continue; + + if (field.FieldType.IsClass) + { + InjectConstructorDependencies(componentSystem, world, field); + } + else + { + if (InjectFromEntityData.SupportsInjections(field)) + InjectFromEntityData.CreateInjection(field, entityManager, injectFromEntity, + injectFromFixedArray); + else + injectGroups.Add( + InjectComponentGroupData.CreateInjection(field.FieldType, field, componentSystem)); + } + } + + outInjectGroups = injectGroups.ToArray(); + + outInjectFromEntityData = + new InjectFromEntityData(injectFromEntity.ToArray(), injectFromFixedArray.ToArray()); + } + + private static void ValidateNoStaticInjectDependencies(Type type) + { +#if UNITY_EDITOR + var fields = type.GetFields(BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic); + + foreach (var field in fields) + if (field.GetCustomAttributes(typeof(InjectAttribute), true).Length != 0) + throw new ArgumentException( + $"[Inject] may not be used on static variables: {GetFieldString(field)}"); +#endif + } + + private static void InjectConstructorDependencies(ScriptBehaviourManager manager, World world, FieldInfo field) + { + if (field.FieldType.IsSubclassOf(typeof(ScriptBehaviourManager))) + field.SetValue(manager, world.GetOrCreateManager(field.FieldType)); + else + ThrowUnsupportedInjectException(field); + } + + public static void ThrowUnsupportedInjectException(FieldInfo field) + { + throw new ArgumentException( + $"[Inject] is not supported for type '{field.FieldType}'. At: {GetFieldString(field)}"); + } + + internal static T[] GetAllInjectedManagers(ScriptBehaviourManager host, World world) + where T : ScriptBehaviourManager + { + var result = new List(); + var fields = host.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + + foreach (var field in fields) + { + var attr = field.GetCustomAttributes(typeof(InjectAttribute), true); + if (attr.Length == 0) + continue; + + if (!field.FieldType.IsClass) + continue; + + if (!field.FieldType.IsSubclassOf(typeof(T))) + continue; + + result.Add((T) world.GetOrCreateManager(field.FieldType)); + } + + return result.ToArray(); + } + } +} diff --git a/Unity.Entities/Injection/ComponentSystemInjection.cs.meta b/Unity.Entities/Injection/ComponentSystemInjection.cs.meta new file mode 100644 index 00000000..365a26b7 --- /dev/null +++ b/Unity.Entities/Injection/ComponentSystemInjection.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 15f0c9e8418343e09f1835b588049d8b +timeCreated: 1511049156 \ No newline at end of file diff --git a/Unity.Entities/Injection/InjectAttribute.cs b/Unity.Entities/Injection/InjectAttribute.cs new file mode 100644 index 00000000..4279b3e9 --- /dev/null +++ b/Unity.Entities/Injection/InjectAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Unity.Entities +{ + [AttributeUsage(AttributeTargets.Field)] + public class InjectAttribute : Attribute + { + } +} diff --git a/Unity.Entities/Injection/InjectAttribute.cs.meta b/Unity.Entities/Injection/InjectAttribute.cs.meta new file mode 100644 index 00000000..35f0db95 --- /dev/null +++ b/Unity.Entities/Injection/InjectAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0cd570c938a441eb828ce5faca7c5418 +timeCreated: 1512064623 \ No newline at end of file diff --git a/Unity.Entities/Injection/InjectComponentFromEntity.cs b/Unity.Entities/Injection/InjectComponentFromEntity.cs new file mode 100644 index 00000000..bb35931b --- /dev/null +++ b/Unity.Entities/Injection/InjectComponentFromEntity.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Reflection; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal struct InjectFromEntityData + { + private readonly InjectionData[] m_InjectComponentDataFromEntity; + private readonly InjectionData[] m_InjectFixedArrayFromEntity; + + public InjectFromEntityData(InjectionData[] componentDataFromEntity, InjectionData[] fixedArrayFromEntity) + { + m_InjectComponentDataFromEntity = componentDataFromEntity; + m_InjectFixedArrayFromEntity = fixedArrayFromEntity; + } + + public static bool SupportsInjections(FieldInfo field) + { + if (field.FieldType.IsGenericType && + field.FieldType.GetGenericTypeDefinition() == typeof(ComponentDataFromEntity<>)) + return true; + if (field.FieldType.IsGenericType && + field.FieldType.GetGenericTypeDefinition() == typeof(FixedArrayFromEntity<>)) + return true; + return false; + } + + public static void CreateInjection(FieldInfo field, EntityManager entityManager, + List componentDataFromEntity, List fixedArrayFromEntity) + { + var isReadOnly = field.GetCustomAttributes(typeof(ReadOnlyAttribute), true).Length != 0; + + if (field.FieldType.IsGenericType && + field.FieldType.GetGenericTypeDefinition() == typeof(ComponentDataFromEntity<>)) + { + var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); + componentDataFromEntity.Add(injection); + } + else if (field.FieldType.IsGenericType && + field.FieldType.GetGenericTypeDefinition() == typeof(FixedArrayFromEntity<>)) + { + var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); + fixedArrayFromEntity.Add(injection); + } + else + { + ComponentSystemInjection.ThrowUnsupportedInjectException(field); + } + } + + public unsafe void UpdateInjection(byte* pinnedSystemPtr, EntityManager entityManager) + { + for (var i = 0; i != m_InjectComponentDataFromEntity.Length; i++) + { + var array = entityManager.GetComponentDataFromEntity( + m_InjectComponentDataFromEntity[i].ComponentType.TypeIndex, + m_InjectComponentDataFromEntity[i].IsReadOnly); + UnsafeUtility.CopyStructureToPtr(ref array, + pinnedSystemPtr + m_InjectComponentDataFromEntity[i].FieldOffset); + } + + for (var i = 0; i != m_InjectFixedArrayFromEntity.Length; i++) + { + var array = entityManager.GetFixedArrayFromEntity( + m_InjectFixedArrayFromEntity[i].ComponentType.TypeIndex, + m_InjectFixedArrayFromEntity[i].IsReadOnly); + UnsafeUtility.CopyStructureToPtr(ref array, + pinnedSystemPtr + m_InjectFixedArrayFromEntity[i].FieldOffset); + } + } + + public void ExtractJobDependencyTypes(ComponentSystemBase system) + { + if (m_InjectComponentDataFromEntity != null) + foreach (var injection in m_InjectComponentDataFromEntity) + system.AddReaderWriter(injection.ComponentType); + + if (m_InjectFixedArrayFromEntity != null) + foreach (var injection in m_InjectFixedArrayFromEntity) + system.AddReaderWriter(injection.ComponentType); + } + } +} diff --git a/Unity.Entities/Injection/InjectComponentFromEntity.cs.meta b/Unity.Entities/Injection/InjectComponentFromEntity.cs.meta new file mode 100644 index 00000000..3676195f --- /dev/null +++ b/Unity.Entities/Injection/InjectComponentFromEntity.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1be2212180ea46758457f3d63f4aabf5 +timeCreated: 1511048217 \ No newline at end of file diff --git a/Unity.Entities/Injection/InjectComponentGroup.cs b/Unity.Entities/Injection/InjectComponentGroup.cs new file mode 100644 index 00000000..986838e1 --- /dev/null +++ b/Unity.Entities/Injection/InjectComponentGroup.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal struct ProxyComponentData : IComponentData + { + } + + internal struct ProxySharedComponentData : ISharedComponentData + { + } + + internal class InjectComponentGroupData + { + private readonly InjectionData[] m_ComponentDataInjections; + + private readonly int m_EntityArrayOffset; + private readonly InjectionData[] m_FixedArrayInjections; + private readonly int m_GroupFieldOffset; + + private readonly InjectionContext m_InjectionContext; + private readonly int m_LengthOffset; + private readonly InjectionData[] m_SharedComponentInjections; + private readonly ComponentGroup m_EntityGroup; + + private InjectComponentGroupData(ComponentSystemBase system, FieldInfo groupField, + InjectionData[] componentDataInjections, InjectionData[] fixedArrayInjections, + InjectionData[] sharedComponentInjections, + FieldInfo entityArrayInjection, FieldInfo indexFromEntityInjection, InjectionContext injectionContext, + FieldInfo lengthInjection, ComponentType[] componentRequirements) + { + m_EntityGroup = system.GetComponentGroupInternal(componentRequirements); + + m_ComponentDataInjections = componentDataInjections; + m_FixedArrayInjections = fixedArrayInjections; + m_SharedComponentInjections = sharedComponentInjections; + m_InjectionContext = injectionContext; + + PatchGetIndexInComponentGroup(m_ComponentDataInjections); + PatchGetIndexInComponentGroup(m_FixedArrayInjections); + PatchGetIndexInComponentGroup(m_SharedComponentInjections); + + injectionContext.PrepareEntries(m_EntityGroup); + + if (entityArrayInjection != null) + m_EntityArrayOffset = UnsafeUtility.GetFieldOffset(entityArrayInjection); + else + m_EntityArrayOffset = -1; + + if (lengthInjection != null) + m_LengthOffset = UnsafeUtility.GetFieldOffset(lengthInjection); + else + m_LengthOffset = -1; + + m_GroupFieldOffset = UnsafeUtility.GetFieldOffset(groupField); + } + + private void PatchGetIndexInComponentGroup(InjectionData[] componentInjections) + { + for (var i = 0; i != componentInjections.Length; i++) + componentInjections[i].IndexInComponentGroup = + m_EntityGroup.GetIndexInComponentGroup(componentInjections[i].ComponentType.TypeIndex); + } + + public unsafe void UpdateInjection(byte* systemPtr) + { + var groupStructPtr = systemPtr + m_GroupFieldOffset; + + int length; + ComponentChunkIterator iterator; + m_EntityGroup.GetComponentChunkIterator(out length, out iterator); + + for (var i = 0; i != m_ComponentDataInjections.Length; i++) + { + ComponentDataArray data; + m_EntityGroup.GetComponentDataArray(ref iterator, m_ComponentDataInjections[i].IndexInComponentGroup, + length, out data); + UnsafeUtility.CopyStructureToPtr(ref data, groupStructPtr + m_ComponentDataInjections[i].FieldOffset); + } + + for (var i = 0; i != m_SharedComponentInjections.Length; i++) + { + SharedComponentDataArray data; + m_EntityGroup.GetSharedComponentDataArray(ref iterator, + m_SharedComponentInjections[i].IndexInComponentGroup, length, out data); + UnsafeUtility.CopyStructureToPtr(ref data, groupStructPtr + m_SharedComponentInjections[i].FieldOffset); + } + + for (var i = 0; i != m_FixedArrayInjections.Length; i++) + { + FixedArrayArray data; + m_EntityGroup.GetFixedArrayArray(ref iterator, m_FixedArrayInjections[i].IndexInComponentGroup, length, + out data); + UnsafeUtility.CopyStructureToPtr(ref data, groupStructPtr + m_FixedArrayInjections[i].FieldOffset); + } + + if (m_EntityArrayOffset != -1) + { + EntityArray entityArray; + m_EntityGroup.GetEntityArray(ref iterator, length, out entityArray); + UnsafeUtility.CopyStructureToPtr(ref entityArray, groupStructPtr + m_EntityArrayOffset); + } + + if (m_InjectionContext.HasEntries) + m_InjectionContext.UpdateEntries(m_EntityGroup, ref iterator, length, groupStructPtr); + + if (m_LengthOffset != -1) UnsafeUtility.CopyStructureToPtr(ref length, groupStructPtr + m_LengthOffset); + } + + public static InjectComponentGroupData CreateInjection(Type injectedGroupType, FieldInfo groupField, + ComponentSystemBase system) + { + FieldInfo entityArrayField; + FieldInfo indexFromEntityField; + FieldInfo lengthField; + + var injectionContext = new InjectionContext(); + var componentDataInjections = new List(); + var fixedArrayInjections = new List(); + var sharedComponentInjections = new List(); + + var componentRequirements = new HashSet(); + var error = CollectInjectedGroup(system, groupField, injectedGroupType, out entityArrayField, + out indexFromEntityField, injectionContext, out lengthField, componentRequirements, + componentDataInjections, fixedArrayInjections, sharedComponentInjections); + if (error != null) + throw new ArgumentException(error); + + return new InjectComponentGroupData(system, groupField, componentDataInjections.ToArray(), + fixedArrayInjections.ToArray(), sharedComponentInjections.ToArray(), entityArrayField, + indexFromEntityField, injectionContext, lengthField, componentRequirements.ToArray()); + } + + private static string CollectInjectedGroup(ComponentSystemBase system, FieldInfo groupField, + Type injectedGroupType, out FieldInfo entityArrayField, out FieldInfo indexFromEntityField, + InjectionContext injectionContext, out FieldInfo lengthField, ISet componentRequirements, + ICollection componentDataInjections, ICollection fixedArrayInjections, + ICollection sharedComponentInjections) + { + //@TODO: Improve error messages... + var fields = + injectedGroupType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + entityArrayField = null; + indexFromEntityField = null; + lengthField = null; + + foreach (var field in fields) + { + var isReadOnly = field.GetCustomAttributes(typeof(ReadOnlyAttribute), true).Length != 0; + //@TODO: Prevent using GameObjectEntity, it will never show up. Point to GameObjectArray instead... + + if (field.FieldType.IsGenericType && + field.FieldType.GetGenericTypeDefinition() == typeof(ComponentDataArray<>)) + { + var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); + componentDataInjections.Add(injection); + componentRequirements.Add(injection.ComponentType); + } + else if (field.FieldType.IsGenericType && + field.FieldType.GetGenericTypeDefinition() == typeof(SubtractiveComponent<>)) + { + componentRequirements.Add(ComponentType.Subtractive(field.FieldType.GetGenericArguments()[0])); + } + else if (field.FieldType.IsGenericType && + field.FieldType.GetGenericTypeDefinition() == typeof(FixedArrayArray<>)) + { + var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); + + fixedArrayInjections.Add(injection); + componentRequirements.Add(injection.ComponentType); + } + else if (field.FieldType.IsGenericType && + field.FieldType.GetGenericTypeDefinition() == typeof(SharedComponentDataArray<>)) + { + if (!isReadOnly) + return + $"{system.GetType().Name}:{groupField.Name} SharedComponentDataArray<> must always be injected as [ReadOnly]"; + var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], true); + + sharedComponentInjections.Add(injection); + componentRequirements.Add(injection.ComponentType); + } + else if (field.FieldType == typeof(EntityArray)) + { + // Error on multiple EntityArray + if (entityArrayField != null) + return + $"{system.GetType().Name}:{groupField.Name} An [Inject] struct, may only contain a single EntityArray"; + + entityArrayField = field; + } + else if (field.FieldType == typeof(int)) + { + // Error on multiple EntityArray + if (field.Name != "Length") + return + $"{system.GetType().Name}:{groupField.Name} An [Inject] struct, supports only a specialized int storing the length of the group. (\"int Length;\")"; + lengthField = field; + } + else + { + var hook = InjectionHookSupport.HookFor(field); + if (hook == null) + return + $"{system.GetType().Name}:{groupField.Name} [Inject] may only be used on ComponentDataArray<>, ComponentArray<>, TransformAccessArray, EntityArray, {string.Join(",", InjectionHookSupport.Hooks.Select(h => h.FieldTypeOfInterest.Name))} and int Length."; + + var error = hook.ValidateField(field, isReadOnly, injectionContext); + if (error != null) return error; + + injectionContext.AddEntry(hook.CreateInjectionInfoFor(field, isReadOnly)); + } + } + + if (injectionContext.HasComponentRequirements) + foreach (var requirement in injectionContext.ComponentRequirements) + componentRequirements.Add(requirement); + + return null; + } + } +} diff --git a/Unity.Entities/Injection/InjectComponentGroup.cs.meta b/Unity.Entities/Injection/InjectComponentGroup.cs.meta new file mode 100644 index 00000000..beb0080a --- /dev/null +++ b/Unity.Entities/Injection/InjectComponentGroup.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e808dc4ed0eb648f1a15659b3a73fc32 +timeCreated: 1491948448 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Injection/InjectionData.cs b/Unity.Entities/Injection/InjectionData.cs new file mode 100644 index 00000000..c7acf191 --- /dev/null +++ b/Unity.Entities/Injection/InjectionData.cs @@ -0,0 +1,24 @@ +using System; +using System.Reflection; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal struct InjectionData + { + public ComponentType ComponentType; + public int IndexInComponentGroup; + public readonly bool IsReadOnly; + public readonly int FieldOffset; + + public InjectionData(FieldInfo field, Type genericType, bool isReadOnly) + { + IndexInComponentGroup = -1; + FieldOffset = UnsafeUtility.GetFieldOffset(field); + + var accessMode = isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite; + ComponentType = new ComponentType(genericType, accessMode); + IsReadOnly = isReadOnly; + } + } +} diff --git a/Unity.Entities/Injection/InjectionData.cs.meta b/Unity.Entities/Injection/InjectionData.cs.meta new file mode 100644 index 00000000..d1d20a95 --- /dev/null +++ b/Unity.Entities/Injection/InjectionData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8f94d591047f4a7cae2b25c92134da90 +timeCreated: 1511048294 \ No newline at end of file diff --git a/Unity.Entities/Injection/InjectionHook.cs b/Unity.Entities/Injection/InjectionHook.cs new file mode 100644 index 00000000..6c200dbc --- /dev/null +++ b/Unity.Entities/Injection/InjectionHook.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Unity.Entities +{ + internal sealed class CustomInjectionHookAttribute : Attribute + { + } + + public sealed class InjectionContext + { + private readonly List m_Entries = new List(); + + public bool HasComponentRequirements { get; private set; } + + public bool HasEntries => m_Entries.Count != 0; + + public IReadOnlyCollection Entries => m_Entries; + + public IEnumerable ComponentRequirements + { + get + { + foreach (var info in m_Entries) + foreach (var requirement in info.ComponentRequirements) + yield return requirement; + } + } + + internal void AddEntry(Entry entry) + { + HasComponentRequirements = HasComponentRequirements || entry.ComponentRequirements.Length > 0; + m_Entries.Add(entry); + } + + public void PrepareEntries(ComponentGroup entityGroup) + { + if (!HasEntries) + return; + + for (var index = 0; index < m_Entries.Count; index++) + { + var entry = m_Entries[index]; + entry.Hook.PrepareEntry(ref entry, entityGroup); + m_Entries[index] = entry; + } + } + + internal unsafe void UpdateEntries(ComponentGroup entityGroup, ref ComponentChunkIterator iterator, int length, + byte* groupStructPtr) + { + if (!HasEntries) + return; + + foreach (var info in m_Entries) + info.Hook.InjectEntry(info, entityGroup, ref iterator, length, groupStructPtr); + } + + public struct Entry + { + public int FieldOffset; + public FieldInfo FieldInfo; + public Type[] ComponentRequirements; + public InjectionHook Hook; + public ComponentType.AccessMode AccessMode; + public int IndexInComponentGroup; + public bool IsReadOnly; + public ComponentType ComponentType; + } + } + + public abstract unsafe class InjectionHook + { + public abstract Type FieldTypeOfInterest { get; } + public abstract bool IsInterestedInField(FieldInfo fieldInfo); + public abstract InjectionContext.Entry CreateInjectionInfoFor(FieldInfo field, bool isReadOnly); + + internal abstract void InjectEntry(InjectionContext.Entry entry, ComponentGroup entityGroup, + ref ComponentChunkIterator iterator, int length, byte* groupStructPtr); + + public abstract string ValidateField(FieldInfo field, bool isReadOnly, InjectionContext injectionInfo); + + public virtual void PrepareEntry(ref InjectionContext.Entry entry, ComponentGroup entityGroup) + { + } + } + + public static class InjectionHookSupport + { + private static bool s_HasHooks; + private static readonly List k_Hooks = new List(); + + internal static IReadOnlyCollection Hooks => k_Hooks; + + public static void RegisterHook(InjectionHook hook) + { + s_HasHooks = true; + k_Hooks.Add(hook); + } + + public static void UnregisterHook(InjectionHook hook) + { + k_Hooks.Remove(hook); + s_HasHooks = k_Hooks.Count != 0; + } + + internal static InjectionHook HookFor(FieldInfo fieldInfo) + { + if (!s_HasHooks) + return null; + + // TODO: in case of multiple hooks interested in a single field type, we should drop an error (in Editor) + foreach (var hook in k_Hooks) + if (hook.IsInterestedInField(fieldInfo)) + return hook; + + return null; + } + + public static bool IsValidHook(Type type) + { + if (type.IsAbstract) + return false; + if (type.ContainsGenericParameters) + return false; + if (!typeof(InjectionHook).IsAssignableFrom(type)) + return false; + return type.GetCustomAttributes(typeof(CustomInjectionHookAttribute), true).Length != 0; + } + } +} diff --git a/Unity.Entities/Injection/InjectionHook.cs.meta b/Unity.Entities/Injection/InjectionHook.cs.meta new file mode 100644 index 00000000..a0bbf3a1 --- /dev/null +++ b/Unity.Entities/Injection/InjectionHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ebb4176a338ea4d3aaed1f846062468e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Injection/World.cs b/Unity.Entities/Injection/World.cs new file mode 100644 index 00000000..89e8751b --- /dev/null +++ b/Unity.Entities/Injection/World.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Reflection; + +namespace Unity.Entities +{ + public class World : IDisposable + { + private static readonly List allWorlds = new List(); + private bool m_AllowGetManager = true; + + //@TODO: What about multiple managers of the same type... + private Dictionary m_BehaviourManagerLookup = + new Dictionary(); + + private List m_BehaviourManagers = new List(); + + private int m_DefaultCapacity = 10; + + public World(string name) + { + // Debug.LogError("Create World "+ name + " - " + GetHashCode()); + Name = name; + allWorlds.Add(this); + } + + public IEnumerable BehaviourManagers => + new ReadOnlyCollection(m_BehaviourManagers); + + public string Name { get; } + + public int Version { get; private set; } + + public static World Active { get; set; } + + public static ReadOnlyCollection AllWorlds => new ReadOnlyCollection(allWorlds); + + internal List Patches { get; } = new List(); + + public bool IsCreated => m_BehaviourManagers != null; + + public void Dispose() + { + if (!IsCreated) + throw new ArgumentException("World is already disposed"); + // Debug.LogError("Dispose World "+ Name + " - " + GetHashCode()); + + if (allWorlds.Contains(this)) + allWorlds.Remove(this); + + foreach (var behaviourManager in Patches) + try + { + behaviourManager.DestroyInstance(); + } + catch (Exception e) + { + Debug.LogException(e); + } + + // Destruction should happen in reverse order to construction + m_BehaviourManagers.Reverse(); + + //@TODO: Crazy hackery to make EntityManager be destroyed last. + foreach (var behaviourManager in m_BehaviourManagers) + if (behaviourManager is EntityManager) + { + m_BehaviourManagers.Remove(behaviourManager); + m_BehaviourManagers.Add(behaviourManager); + break; + } + + m_AllowGetManager = false; + foreach (var behaviourManager in m_BehaviourManagers) + try + { + behaviourManager.DestroyInstance(); + } + catch (Exception e) + { + Debug.LogException(e); + } + + if (Active == this) + Active = null; + + m_BehaviourManagers.Clear(); + m_BehaviourManagerLookup.Clear(); + + m_BehaviourManagers = null; + m_BehaviourManagerLookup = null; + } + + private int GetCapacityForType(Type type) + { + return m_DefaultCapacity; + } + + public void SetDefaultCapacity(int value) + { + m_DefaultCapacity = value; + } + + internal void AddComponentSystemPatch(Type type) + { + var patch = Activator.CreateInstance(type) as ScriptBehaviourManager; + patch.CreateInstance(this, 0); + Patches.Add(patch); + } + + public static void DisposeAllWorlds() + { + while (allWorlds.Count != 0) + allWorlds[0].Dispose(); + } + + private ScriptBehaviourManager CreateManagerInternal(Type type, int capacity, object[] constructorArguments) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!m_AllowGetManager) + throw new ArgumentException( + "During destruction of a system you are not allowed to create more systems."); + + if (constructorArguments != null && constructorArguments.Length != 0) + { + var constructors = + type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + if (constructors.Length == 1 && constructors[0].IsPrivate) + throw new MissingMethodException( + $"Constructing {type} failed because the constructor was private, it must be public."); + } +#endif + + m_AllowGetManager = true; + ScriptBehaviourManager manager; + try + { + manager = Activator.CreateInstance(type, constructorArguments) as ScriptBehaviourManager; + } + catch + { + m_AllowGetManager = false; + throw; + } + + m_BehaviourManagers.Add(manager); + AddTypeLookup(type, manager); + + try + { + manager.CreateInstance(this, capacity); + } + catch + { + RemoveManagerInteral(manager); + throw; + } + + ++Version; + return manager; + } + + private ScriptBehaviourManager GetExistingManagerInternal(Type type) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!IsCreated) + throw new ArgumentException("During destruction "); + if (!m_AllowGetManager) + throw new ArgumentException( + "During destruction of a system you are not allowed to get or create more systems."); +#endif + + ScriptBehaviourManager manager; + if (m_BehaviourManagerLookup.TryGetValue(type, out manager)) + return manager; + + return null; + } + + private ScriptBehaviourManager GetOrCreateManagerInternal(Type type) + { + var manager = GetExistingManagerInternal(type); + + return manager ?? CreateManagerInternal(type, GetCapacityForType(type), null); + } + + private void AddTypeLookup(Type type, ScriptBehaviourManager manager) + { + while (type != typeof(ScriptBehaviourManager)) + { + if (!m_BehaviourManagerLookup.ContainsKey(type)) + m_BehaviourManagerLookup.Add(type, manager); + + type = type.BaseType; + } + } + + private void RemoveManagerInteral(ScriptBehaviourManager manager) + { + if (!m_BehaviourManagers.Remove(manager)) + throw new ArgumentException($"manager does not exist in the world"); + ++Version; + + var type = manager.GetType(); + while (type != typeof(ScriptBehaviourManager)) + { + if (m_BehaviourManagerLookup[type] == manager) + { + m_BehaviourManagerLookup.Remove(type); + + foreach (var otherManager in m_BehaviourManagers) + if (otherManager.GetType().IsSubclassOf(type)) + AddTypeLookup(otherManager.GetType(), otherManager); + } + + type = type.BaseType; + } + } + + public ScriptBehaviourManager CreateManager(Type type, params object[] constructorArgumnents) + { + return CreateManagerInternal(type, GetCapacityForType(type), constructorArgumnents); + } + + public T CreateManager(params object[] constructorArgumnents) where T : ScriptBehaviourManager + { + return (T) CreateManagerInternal(typeof(T), GetCapacityForType(typeof(T)), constructorArgumnents); + } + + public T GetOrCreateManager() where T : ScriptBehaviourManager + { + return (T) GetOrCreateManagerInternal(typeof(T)); + } + + public ScriptBehaviourManager GetOrCreateManager(Type type) + { + return GetOrCreateManagerInternal(type); + } + + public T GetExistingManager() where T : ScriptBehaviourManager + { + return (T) GetExistingManagerInternal(typeof(T)); + } + + public ScriptBehaviourManager GetExistingManager(Type type) + { + return GetExistingManagerInternal(type); + } + + public void DestroyManager(ScriptBehaviourManager manager) + { + RemoveManagerInteral(manager); + manager.DestroyInstance(); + } + } +} diff --git a/Unity.Entities/Injection/World.cs.meta b/Unity.Entities/Injection/World.cs.meta new file mode 100644 index 00000000..af7d0dd9 --- /dev/null +++ b/Unity.Entities/Injection/World.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 41a4159a79cf34564832f8a0a2bf1dd3 +timeCreated: 1482868705 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Injection/WorldDebuggingTools.cs b/Unity.Entities/Injection/WorldDebuggingTools.cs new file mode 100644 index 00000000..a92536ce --- /dev/null +++ b/Unity.Entities/Injection/WorldDebuggingTools.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Collections; + +namespace Unity.Entities +{ + internal class WorldDebuggingTools + { + internal static void MatchEntityInComponentGroups(World world, Entity entity, + List>> matchList) + { + using (var entityComponentTypes = + world.GetExistingManager().GetComponentTypes(entity, Allocator.Temp)) + { + foreach (var manager in World.Active.BehaviourManagers) + { + var componentGroupList = new List(); + var system = manager as ComponentSystemBase; + if (system == null) continue; + foreach (var componentGroup in system.ComponentGroups) + if (Match(componentGroup, entityComponentTypes)) + componentGroupList.Add(componentGroup); + + if (componentGroupList.Count > 0) + matchList.Add( + new Tuple>(manager, componentGroupList)); + } + } + } + + private static bool Match(ComponentGroup group, NativeArray entityComponentTypes) + { + foreach (var groupType in group.Types.Skip(1)) + { + var found = false; + foreach (var type in entityComponentTypes) + { + if (type.TypeIndex != groupType.TypeIndex) + continue; + found = true; + break; + } + + if (found == (groupType.AccessModeType == ComponentType.AccessMode.Subtractive)) + return false; + } + + return true; + } + } +} diff --git a/Unity.Entities/Injection/WorldDebuggingTools.cs.meta b/Unity.Entities/Injection/WorldDebuggingTools.cs.meta new file mode 100644 index 00000000..1d8a1658 --- /dev/null +++ b/Unity.Entities/Injection/WorldDebuggingTools.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 768201aca867d49b9a075bea7b9b41cf +timeCreated: 1482868705 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators.meta b/Unity.Entities/Iterators.meta new file mode 100644 index 00000000..25c43e55 --- /dev/null +++ b/Unity.Entities/Iterators.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5c4be65db207545baa99a7c052845069 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/ArchetypeChunkArray.cs b/Unity.Entities/Iterators/ArchetypeChunkArray.cs new file mode 100644 index 00000000..e196737a --- /dev/null +++ b/Unity.Entities/Iterators/ArchetypeChunkArray.cs @@ -0,0 +1,308 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + public unsafe struct ArchetypeChunk + { + [NativeDisableUnsafePtrRestriction] internal Chunk* m_Chunk; + public int StartIndex; + public int Count => m_Chunk->Count; + + public NativeArray GetNativeArray(ArchetypeChunkEntityType archetypeChunkEntityType) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(archetypeChunkEntityType.m_Safety); +#endif + var archetype = m_Chunk->Archetype; + var buffer = m_Chunk->Buffer; + var length = m_Chunk->Count; + var startOffset = archetype->Offsets[0]; + var result = + NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(buffer + startOffset, length, + Allocator.Invalid); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref result, archetypeChunkEntityType.m_Safety); +#endif + return result; + } + + private int GetIndexInArchetype(int typeIndex) + { + var typeIndexInArchetype = 1; + var archetype = m_Chunk->Archetype; + + while (archetype->Types[typeIndexInArchetype].TypeIndex != typeIndex) + { + ++typeIndexInArchetype; + + if (typeIndexInArchetype == archetype->TypesCount) return -1; + } + + return typeIndexInArchetype; + } + + public uint GetComponentVersion(ArchetypeChunkComponentType chunkComponentType) + where T : struct, IComponentData + { + var typeIndex = chunkComponentType.m_TypeIndex; + var typeIndexInArchetype = GetIndexInArchetype(typeIndex); + if (typeIndexInArchetype == -1) return 0; + return m_Chunk->ChangeVersion[typeIndexInArchetype]; + } + + public int GetSharedComponentIndex(ArchetypeChunkSharedComponentType chunkSharedComponentData) + where T : struct, ISharedComponentData + { + var archetype = m_Chunk->Archetype; + var typeIndex = chunkSharedComponentData.m_TypeIndex; + var typeIndexInArchetype = GetIndexInArchetype(typeIndex); + if (typeIndexInArchetype == -1) return -1; + + var chunkSharedComponentIndex = archetype->SharedComponentOffset[typeIndexInArchetype]; + var sharedComponentIndex = m_Chunk->SharedComponentValueArray[chunkSharedComponentIndex]; + return sharedComponentIndex; + } + + public NativeArray GetNativeArray(ArchetypeChunkComponentType chunkComponentType) + where T : struct, IComponentData + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(chunkComponentType.m_Safety); +#endif + var archetype = m_Chunk->Archetype; + var typeIndex = chunkComponentType.m_TypeIndex; + var typeIndexInArchetype = GetIndexInArchetype(typeIndex); + if (typeIndexInArchetype == -1) + { + var emptyResult = + NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(null, 0, Allocator.Invalid); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref emptyResult, chunkComponentType.m_Safety); +#endif + return emptyResult; + } + + var buffer = m_Chunk->Buffer; + var length = m_Chunk->Count; + var startOffset = archetype->Offsets[typeIndexInArchetype]; + var result = + NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(buffer + startOffset, length, + Allocator.Invalid); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref result, chunkComponentType.m_Safety); +#endif + if (!chunkComponentType.IsReadOnly) + m_Chunk->ChangeVersion[typeIndex] = chunkComponentType.GlobalSystemVersion; + return result; + } + } + + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + public unsafe struct ArchetypeChunkArray : IDisposable + { + [NativeDisableUnsafePtrRestriction] private readonly ArchetypeChunk* m_Chunks; + private readonly Allocator m_Allocator; + + public int EntityCount { get; } + + private readonly int m_Length; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly int m_MinIndex; + private readonly int m_MaxIndex; + private readonly AtomicSafetyHandle m_Safety; +#endif + + public int Length => m_Length; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal ArchetypeChunkArray(NativeList archetypes, Allocator allocator, + AtomicSafetyHandle safety) +#else + internal ArchetypeChunkArray(NativeList archetypes, Allocator allocator) +#endif + { + var archetypeCount = archetypes.Length; + + m_Length = 0; + EntityCount = 0; + for (var i = 0; i < archetypeCount; i++) + { + m_Length += archetypes[i].Archetype->ChunkCount; + EntityCount += archetypes[i].Archetype->EntityCount; + } + + m_Chunks = (ArchetypeChunk*) UnsafeUtility.Malloc(UnsafeUtility.SizeOf() * m_Length, 16, + allocator); + m_Allocator = allocator; + + if (m_Length > 0) + { + var lastChunk = (Chunk*) archetypes[0].Archetype->ChunkList.Begin; + var lastArchetypeIndex = 0; + var lastChunkOffset = 0; + var chunkCount = 1; + m_Chunks[0] = new ArchetypeChunk {m_Chunk = lastChunk, StartIndex = lastChunkOffset}; + for (var i = 1; i < m_Length; i++) + { + lastChunkOffset += lastChunk->Count; + lastChunk = (Chunk*) lastChunk->ChunkListNode.Next; + if (lastChunk == (Chunk*) archetypes[lastArchetypeIndex].Archetype->ChunkList.End) + { + lastArchetypeIndex++; + + if (lastArchetypeIndex == archetypeCount) + break; + + lastChunk = (Chunk*) archetypes[lastArchetypeIndex].Archetype->ChunkList.Begin; + } + + m_Chunks[i] = new ArchetypeChunk {m_Chunk = lastChunk, StartIndex = lastChunkOffset}; + chunkCount++; + } + + m_Length = chunkCount; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_MinIndex = 0; + m_MaxIndex = m_Length - 1; + m_Safety = safety; +#endif + } + + public void Dispose() + { + UnsafeUtility.Free(m_Chunks, m_Allocator); + } + + public ArchetypeChunk this[int index] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + if (index < m_MinIndex || index > m_MaxIndex) + FailOutOfRangeError(index); +#endif + return m_Chunks[index]; + } + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private void FailOutOfRangeError(int index) + { + //@TODO: Make error message utility and share with NativeArray... + if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1)) + throw new IndexOutOfRangeException( + $"Index {index} is out of restricted IJobParallelFor range [{m_MinIndex}...{m_MaxIndex}] in ReadWriteBuffer.\nReadWriteBuffers are restricted to only read & write the element at the job index. You can use double buffering strategies to avoid race conditions due to reading & writing in parallel to the same elements from a job."); + + throw new IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length."); + } +#endif + } + + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + public struct ArchetypeChunkComponentType + where T : struct, IComponentData + { + internal readonly int m_TypeIndex; + internal readonly uint m_GlobalSystemVersion; + internal readonly bool m_IsReadOnly; + + public uint GlobalSystemVersion => m_GlobalSystemVersion; + public bool IsReadOnly => m_IsReadOnly; + +#pragma warning disable 0414 + private readonly int m_Length; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly int m_MinIndex; + private readonly int m_MaxIndex; + internal readonly AtomicSafetyHandle m_Safety; +#endif +#pragma warning restore 0414 + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal ArchetypeChunkComponentType(AtomicSafetyHandle safety, bool isReadOnly, uint globalSystemVersion) +#else + internal ArchetypeChunkComponentType(bool isReadOnly,uint globalSystemVersion) +#endif + { + m_Length = 1; + m_TypeIndex = TypeManager.GetTypeIndex(); + m_GlobalSystemVersion = globalSystemVersion; + m_IsReadOnly = isReadOnly; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_MinIndex = 0; + m_MaxIndex = 0; + m_Safety = safety; +#endif + } + } + + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + public struct ArchetypeChunkSharedComponentType + where T : struct, ISharedComponentData + { + internal readonly int m_TypeIndex; + +#pragma warning disable 0414 + private readonly int m_Length; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly int m_MinIndex; + private readonly int m_MaxIndex; + internal readonly AtomicSafetyHandle m_Safety; +#endif +#pragma warning restore 0414 + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal ArchetypeChunkSharedComponentType(AtomicSafetyHandle safety) +#else + internal unsafe ArchetypeChunkSharedComponentType(bool unused = false) +#endif + { + m_Length = 1; + m_TypeIndex = TypeManager.GetTypeIndex(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_MinIndex = 0; + m_MaxIndex = 0; + m_Safety = safety; +#endif + } + } + + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + public struct ArchetypeChunkEntityType + { +#pragma warning disable 0414 + private readonly int m_Length; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly int m_MinIndex; + private readonly int m_MaxIndex; + internal readonly AtomicSafetyHandle m_Safety; +#endif +#pragma warning restore 0414 + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal ArchetypeChunkEntityType(AtomicSafetyHandle safety) +#else + internal unsafe ArchetypeChunkEntityType(bool unused = false) +#endif + { + m_Length = 1; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_MinIndex = 0; + m_MaxIndex = 0; + m_Safety = safety; +#endif + } + } +} diff --git a/Unity.Entities/Iterators/ArchetypeChunkArray.cs.meta b/Unity.Entities/Iterators/ArchetypeChunkArray.cs.meta new file mode 100644 index 00000000..3720ba9c --- /dev/null +++ b/Unity.Entities/Iterators/ArchetypeChunkArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc17e6cffe5b94faf84d2e05d31fcdaa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/ComponentChunkIterator.cs b/Unity.Entities/Iterators/ComponentChunkIterator.cs new file mode 100644 index 00000000..1456e604 --- /dev/null +++ b/Unity.Entities/Iterators/ComponentChunkIterator.cs @@ -0,0 +1,411 @@ +using System; +using Unity.Assertions; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal enum FilterType + { + None, + SharedComponent, + Changed, + IndexList + } + + //@TODO: Use field offset / union here... There seems to be an issue in mono preventing it... + internal unsafe struct ComponentGroupFilter + { + public struct IndexList + { + public int Length; + [NativeDisableUnsafePtrRestriction] public int* Indices; + } + + public struct SharedComponentData + { + public int Count; + public fixed int IndexInComponentGroup[2]; + public fixed int SharedComponentIndex[2]; + } + + public struct ChangedFilter + { + public const int Capacity = 2; + + public int Count; + public fixed int IndexInComponentGroup[2]; + } + + + public FilterType Type; + public uint RequiredChangeVersion; + + + public SharedComponentData Shared; + public ChangedFilter Changed; + public IndexList Indices; + + public bool RequiresMatchesFilter => Type == FilterType.SharedComponent || Type == FilterType.Changed; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public void AssertValid() + { + if (Type == FilterType.SharedComponent) + Assert.IsTrue(Shared.Count < 2 && Shared.Count > 0); + else if (Type == FilterType.Changed) + Assert.IsTrue(Changed.Count < 2 && Changed.Count > 0); + } +#endif + } + + internal unsafe struct ComponentChunkCache + { + [NativeDisableUnsafePtrRestriction] public void* CachedPtr; + public int CachedBeginIndex; + public int CachedEndIndex; + public int CachedSizeOf; + } + + internal unsafe struct ComponentChunkIterator + { + [NativeDisableUnsafePtrRestriction] private readonly MatchingArchetypes* m_FirstMatchingArchetype; + [NativeDisableUnsafePtrRestriction] private MatchingArchetypes* m_CurrentMatchingArchetype; + public int IndexInComponentGroup; + private int m_CurrentArchetypeIndex; + [NativeDisableUnsafePtrRestriction] private Chunk* m_CurrentChunk; + private int m_CurrentChunkIndex; + + private ComponentGroupFilter m_Filter; + + private readonly uint m_GlobalSystemVersion; + + internal int GetSharedComponentFromCurrentChunk(int sharedComponentIndex) + { + var archetype = m_CurrentMatchingArchetype->Archetype; + var indexInArchetype = m_CurrentMatchingArchetype->TypeIndexInArchetypeArray[sharedComponentIndex]; + var sharedComponentOffset = archetype->SharedComponentOffset[indexInArchetype]; + return m_CurrentChunk->SharedComponentValueArray[sharedComponentOffset]; + } + + public ComponentChunkIterator(MatchingArchetypes* match, uint globalSystemVersion, + ref ComponentGroupFilter filter) + { + m_FirstMatchingArchetype = match; + m_CurrentMatchingArchetype = match; + IndexInComponentGroup = -1; + m_CurrentChunk = null; + m_CurrentArchetypeIndex = 0; + m_CurrentChunkIndex = 0; + m_GlobalSystemVersion = globalSystemVersion; + m_Filter = filter; + } + + public object GetManagedObject(ArchetypeManager typeMan, int typeIndexInArchetype, int cachedBeginIndex, + int index) + { + return typeMan.GetManagedObject(m_CurrentChunk, typeIndexInArchetype, index - cachedBeginIndex); + } + + public object GetManagedObject(ArchetypeManager typeMan, int cachedBeginIndex, int index) + { + return typeMan.GetManagedObject(m_CurrentChunk, + m_CurrentMatchingArchetype->TypeIndexInArchetypeArray[IndexInComponentGroup], index - cachedBeginIndex); + } + + public object[] GetManagedObjectRange(ArchetypeManager typeMan, int cachedBeginIndex, int index, + out int rangeStart, out int rangeLength) + { + var objs = typeMan.GetManagedObjectRange(m_CurrentChunk, + m_CurrentMatchingArchetype->TypeIndexInArchetypeArray[IndexInComponentGroup], out rangeStart, + out rangeLength); + rangeStart += index - cachedBeginIndex; + rangeLength -= index - cachedBeginIndex; + return objs; + } + + internal static int CalculateNumberOfChunksWithoutFiltering(MatchingArchetypes* firstMatchingArchetype) + { + var chunkCount = 0; + + for (var match = firstMatchingArchetype; match != null; match = match->Next) + chunkCount += match->Archetype->ChunkCount; + + return chunkCount; + } + + public static int CalculateLength(MatchingArchetypes* firstMatchingArchetype, ref ComponentGroupFilter filter) + { + if (filter.Type == FilterType.IndexList) + return filter.Indices.Length; + + // Update the archetype segments + var length = 0; + if (!filter.RequiresMatchesFilter) + for (var match = firstMatchingArchetype; match != null; match = match->Next) + length += match->Archetype->EntityCount; + else + for (var match = firstMatchingArchetype; match != null; match = match->Next) + { + if (match->Archetype->EntityCount <= 0) + continue; + + var archeType = match->Archetype; + for (var c = (Chunk*) archeType->ChunkList.Begin; + c != archeType->ChunkList.End; + c = (Chunk*) c->ChunkListNode.Next) + { + if (!c->MatchesFilter(match, ref filter)) + continue; + + Assert.IsTrue(c->Count > 0); + + length += c->Count; + } + } + + return length; + } + + public static void CalculateInitialChunkIterators(MatchingArchetypes* firstMatchingArchetype, + int indexInComponentGroup, NativeArray sharedComponentIndex, + NativeArray outFirstArchetype, NativeArray outFirstChunk, NativeArray outLength) + { + var lookup = new NativeHashMap(sharedComponentIndex.Length, Allocator.Temp); + for (var f = 0; f < sharedComponentIndex.Length; ++f) lookup.TryAdd(sharedComponentIndex[f], f); + for (var match = firstMatchingArchetype; match != null; match = match->Next) + { + if (match->Archetype->EntityCount <= 0) + continue; + + var archeType = match->Archetype; + for (var c = (Chunk*) archeType->ChunkList.Begin; + c != archeType->ChunkList.End; + c = (Chunk*) c->ChunkListNode.Next) + { + if (c->Count <= 0) + continue; + + var chunkSharedComponentIndex = c->GetSharedComponentIndex(match, indexInComponentGroup); + int filterIndex; + if (!lookup.TryGetValue(chunkSharedComponentIndex, out filterIndex)) + continue; + + + outLength[filterIndex] = outLength[filterIndex] + c->Count; + if (outFirstChunk[filterIndex] != IntPtr.Zero) + continue; + outFirstArchetype[filterIndex] = (IntPtr) match; + outFirstChunk[filterIndex] = (IntPtr) c; + } + } + + lookup.Dispose(); + } + + private void MoveToNextMatchingChunk() + { + var m = m_CurrentMatchingArchetype; + var c = m_CurrentChunk; + var e = (Chunk*) m->Archetype->ChunkList.End; + + do + { + c = (Chunk*) c->ChunkListNode.Next; + while (c == e) + { + m_CurrentArchetypeIndex += m_CurrentChunkIndex; + m_CurrentChunkIndex = 0; + m = m->Next; + if (m == null) + { + m_CurrentMatchingArchetype = null; + m_CurrentChunk = null; + return; + } + + c = (Chunk*) m->Archetype->ChunkList.Begin; + e = (Chunk*) m->Archetype->ChunkList.End; + } + } while (!(c->MatchesFilter(m, ref m_Filter) && c->Capacity > 0)); + + m_CurrentMatchingArchetype = m; + m_CurrentChunk = c; + } + + public void UpdateCacheResolvedIndex(int index, out ComponentChunkCache cache, bool isWriting) + { + Assert.IsTrue(-1 != IndexInComponentGroup); + + if (!m_Filter.RequiresMatchesFilter) + { + if (index < m_CurrentArchetypeIndex || m_CurrentChunk == null) + { + m_CurrentMatchingArchetype = m_FirstMatchingArchetype; + m_CurrentArchetypeIndex = 0; + m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin; + m_CurrentChunkIndex = 0; + } + + while (index >= m_CurrentArchetypeIndex + m_CurrentMatchingArchetype->Archetype->EntityCount) + { + m_CurrentArchetypeIndex += m_CurrentMatchingArchetype->Archetype->EntityCount; + m_CurrentMatchingArchetype = m_CurrentMatchingArchetype->Next; + m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin; + m_CurrentChunkIndex = 0; + } + + index -= m_CurrentArchetypeIndex; + if (index < m_CurrentChunkIndex) + { + m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin; + m_CurrentChunkIndex = 0; + } + + while (index >= m_CurrentChunkIndex + m_CurrentChunk->Count) + { + m_CurrentChunkIndex += m_CurrentChunk->Count; + m_CurrentChunk = (Chunk*) m_CurrentChunk->ChunkListNode.Next; + } + } + else + { + if (index < m_CurrentArchetypeIndex + m_CurrentChunkIndex || m_CurrentChunk == null) + { + if (index < m_CurrentArchetypeIndex) + { + m_CurrentMatchingArchetype = m_FirstMatchingArchetype; + m_CurrentArchetypeIndex = 0; + } + + m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin; + m_CurrentChunkIndex = 0; + if (!(m_CurrentChunk->MatchesFilter(m_CurrentMatchingArchetype, ref m_Filter) && + m_CurrentChunk->Count > 0)) + MoveToNextMatchingChunk(); + } + + while (index >= m_CurrentArchetypeIndex + m_CurrentChunkIndex + m_CurrentChunk->Count) + { + m_CurrentChunkIndex += m_CurrentChunk->Count; + MoveToNextMatchingChunk(); + } + } + + var archetype = m_CurrentMatchingArchetype->Archetype; + var typeIndexInArchetype = m_CurrentMatchingArchetype->TypeIndexInArchetypeArray[IndexInComponentGroup]; + + cache.CachedBeginIndex = m_CurrentChunkIndex + m_CurrentArchetypeIndex; + cache.CachedEndIndex = cache.CachedBeginIndex + m_CurrentChunk->Count; + cache.CachedSizeOf = archetype->SizeOfs[typeIndexInArchetype]; + cache.CachedPtr = m_CurrentChunk->Buffer + archetype->Offsets[typeIndexInArchetype] - + cache.CachedBeginIndex * cache.CachedSizeOf; + + if (isWriting) + m_CurrentChunk->ChangeVersion[typeIndexInArchetype] = m_GlobalSystemVersion; + } + + public void UpdateCache(int index, out ComponentChunkCache cache, bool isWriting) + { + if (m_Filter.Type == FilterType.IndexList) + { + var remappedIndex = m_Filter.Indices.Indices[index]; + + UpdateCacheResolvedIndex(remappedIndex, out cache, isWriting); + + // Find consecutive range of indices + var maxCount = Math.Min(cache.CachedEndIndex - remappedIndex, m_Filter.Indices.Length - index); + var i = 1; + while (i < maxCount) + { + if (m_Filter.Indices.Indices[index + i] != remappedIndex + i) + break; + + i++; + } + + cache.CachedBeginIndex = index; + cache.CachedEndIndex = index + i; + cache.CachedPtr = (byte*) cache.CachedPtr + (remappedIndex - index) * cache.CachedSizeOf; + } + else + { + UpdateCacheResolvedIndex(index, out cache, isWriting); + } + } + + public void MoveToChunkByIndex(int index) + { + if (index < m_CurrentArchetypeIndex || m_CurrentChunk == null) + { + m_CurrentMatchingArchetype = m_FirstMatchingArchetype; + m_CurrentArchetypeIndex = 0; + m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin; + m_CurrentChunkIndex = 0; + } + + while (index >= m_CurrentArchetypeIndex + m_CurrentMatchingArchetype->Archetype->ChunkCount) + { + m_CurrentArchetypeIndex += m_CurrentMatchingArchetype->Archetype->ChunkCount; + m_CurrentMatchingArchetype = m_CurrentMatchingArchetype->Next; + m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin; + m_CurrentChunkIndex = 0; + } + + index -= m_CurrentArchetypeIndex; + if (index < m_CurrentChunkIndex) + { + m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin; + m_CurrentChunkIndex = 0; + } + + while (index >= m_CurrentChunkIndex + 1) + { + m_CurrentChunkIndex += 1; + m_CurrentChunk = (Chunk*) m_CurrentChunk->ChunkListNode.Next; + } + } + + public bool IsCurrentChunkChanged() + { + var typeIndexInArchetype = m_CurrentMatchingArchetype->TypeIndexInArchetypeArray[IndexInComponentGroup]; + return ChangeVersionUtility.DidChange(m_CurrentChunk->ChangeVersion[typeIndexInArchetype], + m_Filter.RequiredChangeVersion); + } + + public void UpdateCacheToCurrentChunk(out ComponentChunkCache cache, bool isWriting) + { + var archetype = m_CurrentMatchingArchetype->Archetype; + var typeIndexInArchetype = m_CurrentMatchingArchetype->TypeIndexInArchetypeArray[IndexInComponentGroup]; + + cache.CachedBeginIndex = 0; + cache.CachedEndIndex = m_CurrentChunk->Count; + cache.CachedSizeOf = archetype->SizeOfs[typeIndexInArchetype]; + cache.CachedPtr = m_CurrentChunk->Buffer + archetype->Offsets[typeIndexInArchetype]; + + if (isWriting) + m_CurrentChunk->ChangeVersion[typeIndexInArchetype] = m_GlobalSystemVersion; + } + + public void GetCacheForType(int componentType, bool isWriting, out ComponentChunkCache cache, + out int indexInArchetype) + { + var archetype = m_CurrentMatchingArchetype->Archetype; + + indexInArchetype = ChunkDataUtility.GetIndexInTypeArray(archetype, componentType); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (indexInArchetype == -1) + throw new ArgumentException("componentType does not exist in the iterated archetype"); +#endif + + cache.CachedBeginIndex = m_CurrentChunkIndex + m_CurrentArchetypeIndex; + cache.CachedEndIndex = cache.CachedBeginIndex + m_CurrentChunk->Count; + cache.CachedSizeOf = archetype->SizeOfs[indexInArchetype]; + cache.CachedPtr = m_CurrentChunk->Buffer + archetype->Offsets[indexInArchetype] - + cache.CachedBeginIndex * cache.CachedSizeOf; + + if (isWriting) + m_CurrentChunk->ChangeVersion[indexInArchetype] = m_GlobalSystemVersion; + } + } +} diff --git a/Unity.Entities/Iterators/ComponentChunkIterator.cs.meta b/Unity.Entities/Iterators/ComponentChunkIterator.cs.meta new file mode 100644 index 00000000..88c4ee8f --- /dev/null +++ b/Unity.Entities/Iterators/ComponentChunkIterator.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: ff385cb4139b749b387eab82fb6ed82e +timeCreated: 1504790404 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/ComponentDataArray.cs b/Unity.Entities/Iterators/ComponentDataArray.cs new file mode 100644 index 00000000..9a68806e --- /dev/null +++ b/Unity.Entities/Iterators/ComponentDataArray.cs @@ -0,0 +1,157 @@ +using System; +using System.Diagnostics; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + public unsafe struct ComponentDataArray where T : struct, IComponentData + { + private ComponentChunkIterator m_Iterator; + private ComponentChunkCache m_Cache; + + private readonly int m_Length; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly int m_MinIndex; + private readonly int m_MaxIndex; + private readonly AtomicSafetyHandle m_Safety; +#endif + public int Length => m_Length; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal ComponentDataArray(ComponentChunkIterator iterator, int length, AtomicSafetyHandle safety) +#else + internal ComponentDataArray(ComponentChunkIterator iterator, int length) +#endif + { + m_Iterator = iterator; + m_Cache = default(ComponentChunkCache); + + m_Length = length; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_MinIndex = 0; + m_MaxIndex = length - 1; + m_Safety = safety; +#endif + } + + internal void* GetUnsafeChunkPtr(int startIndex, int maxCount, out int actualCount, bool isWriting) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + GetUnsafeChunkPtrCheck(startIndex, maxCount); +#endif + + m_Iterator.UpdateCache(startIndex, out m_Cache, isWriting); + + void* ptr = (byte*) m_Cache.CachedPtr + startIndex * m_Cache.CachedSizeOf; + actualCount = Math.Min(maxCount, m_Cache.CachedEndIndex - startIndex); + + return ptr; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void GetUnsafeChunkPtrCheck(int startIndex, int maxCount) + { + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + + if (startIndex < m_MinIndex) + FailOutOfRangeError(startIndex); + else if (startIndex + maxCount > m_MaxIndex + 1) + FailOutOfRangeError(startIndex + maxCount); + } +#endif + + public NativeArray GetChunkArray(int startIndex, int maxCount) + { + int count; + //@TODO: How should we declare read / write here? + var ptr = GetUnsafeChunkPtr(startIndex, maxCount, out count, true); + + var arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, count, Allocator.Invalid); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, m_Safety); +#endif + + return arr; + } + + public void CopyTo(NativeSlice dst, int startIndex = 0) + { + var copiedCount = 0; + while (copiedCount < dst.Length) + { + var chunkArray = GetChunkArray(startIndex + copiedCount, dst.Length - copiedCount); + dst.Slice(copiedCount, chunkArray.Length).CopyFrom(chunkArray); + + copiedCount += chunkArray.Length; + } + } + + public T this[int index] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + GetValueCheck(index); +#endif + + if (index < m_Cache.CachedBeginIndex || index >= m_Cache.CachedEndIndex) + m_Iterator.UpdateCache(index, out m_Cache, false); + + return UnsafeUtility.ReadArrayElement(m_Cache.CachedPtr, index); + } + + set + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + SetValueCheck(index); +#endif + + if (index < m_Cache.CachedBeginIndex || index >= m_Cache.CachedEndIndex) + m_Iterator.UpdateCache(index, out m_Cache, true); + + UnsafeUtility.WriteArrayElement(m_Cache.CachedPtr, index, value); + } + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void SetValueCheck(int index) + { + AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); + if (index < m_MinIndex || index > m_MaxIndex) + FailOutOfRangeError(index); + } +#endif + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void GetValueCheck(int index) + { + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + if (index < m_MinIndex || index > m_MaxIndex) + FailOutOfRangeError(index); + } +#endif + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void FailOutOfRangeError(int index) + { + //@TODO: Make error message utility and share with NativeArray... + if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1)) + throw new IndexOutOfRangeException( + $"Index {index} is out of restricted IJobParallelFor range [{m_MinIndex}...{m_MaxIndex}] in ReadWriteBuffer.\n" + + "ReadWriteBuffers are restricted to only read & write the element at the job index. " + + "You can use double buffering strategies to avoid race conditions due to " + + "reading & writing in parallel to the same elements from a job."); + + throw new IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length."); + } +#endif + } +} diff --git a/Unity.Entities/Iterators/ComponentDataArray.cs.meta b/Unity.Entities/Iterators/ComponentDataArray.cs.meta new file mode 100644 index 00000000..85a8eb73 --- /dev/null +++ b/Unity.Entities/Iterators/ComponentDataArray.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: e899c8ac6ed7b40afacf51d570844e48 +timeCreated: 1504713176 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/ComponentDataFromEntity.cs b/Unity.Entities/Iterators/ComponentDataFromEntity.cs new file mode 100644 index 00000000..40ff0bf3 --- /dev/null +++ b/Unity.Entities/Iterators/ComponentDataFromEntity.cs @@ -0,0 +1,73 @@ +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + [NativeContainer] + public unsafe struct ComponentDataFromEntity where T : struct, IComponentData + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + readonly AtomicSafetyHandle m_Safety; +#endif + [NativeDisableUnsafePtrRestriction] + readonly EntityDataManager* m_Entities; + readonly int m_TypeIndex; + readonly uint m_GlobalSystemVersion; + int m_TypeLookupCache; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal ComponentDataFromEntity(int typeIndex, EntityDataManager* entityData, AtomicSafetyHandle safety) + { + m_Safety = safety; + m_TypeIndex = typeIndex; + m_Entities = entityData; + m_TypeLookupCache = 0; + m_GlobalSystemVersion = entityData->GlobalSystemVersion; + } +#else + internal ComponentDataFromEntity(int typeIndex, EntityDataManager* entityData) + { + m_TypeIndex = typeIndex; + m_Entities = entityData; + m_TypeLookupCache = 0; + m_GlobalSystemVersion = entityData->GlobalSystemVersion; + } +#endif + + public bool Exists(Entity entity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); +#endif + //@TODO: out of bounds index checks... + + return m_Entities->HasComponent(entity, m_TypeIndex); + } + + public T this[Entity entity] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); +#endif + m_Entities->AssertEntityHasComponent(entity, m_TypeIndex); + + void* ptr = m_Entities->GetComponentDataWithTypeRO(entity, m_TypeIndex, ref m_TypeLookupCache); + T data; + UnsafeUtility.CopyPtrToStructure(ptr, out data); + + return data; + } + set + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); +#endif + m_Entities->AssertEntityHasComponent(entity, m_TypeIndex); + + void* ptr = m_Entities->GetComponentDataWithTypeRW(entity, m_TypeIndex, m_GlobalSystemVersion, ref m_TypeLookupCache); + UnsafeUtility.CopyStructureToPtr(ref value, ptr); + } + } + } +} diff --git a/Unity.Entities/Iterators/ComponentDataFromEntity.cs.meta b/Unity.Entities/Iterators/ComponentDataFromEntity.cs.meta new file mode 100644 index 00000000..da57d2c3 --- /dev/null +++ b/Unity.Entities/Iterators/ComponentDataFromEntity.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 771ae7c34132f460da0c280effdbacef +timeCreated: 1504713176 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/ComponentGroup.cs b/Unity.Entities/Iterators/ComponentGroup.cs new file mode 100644 index 00000000..18996ab3 --- /dev/null +++ b/Unity.Entities/Iterators/ComponentGroup.cs @@ -0,0 +1,570 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; + +namespace Unity.Entities +{ + public struct ForEachComponentGroupFilter : IDisposable + { + internal NativeArray ItemIterator; + internal NativeArray ItemLength; + + internal int IndexInComponentGroup; + internal NativeArray SharedComponentIndex; + + internal ArchetypeManager TypeManager; + + public int Length => ItemIterator.Length; + + public void Dispose() + { + for (var i = 0; i < SharedComponentIndex.Length; ++i) + TypeManager.GetSharedComponentDataManager().RemoveReference(SharedComponentIndex[i]); + ItemIterator.Dispose(); + ItemLength.Dispose(); + SharedComponentIndex.Dispose(); + } + } + + internal unsafe struct ComponentGroupData + { + private readonly EntityGroupData* m_GroupData; + private readonly EntityDataManager* m_EntityDataManager; + private ComponentGroupFilter m_Filter; + + internal ComponentGroupData(EntityGroupData* groupData, EntityDataManager* entityDataManager) + { + m_GroupData = groupData; + m_EntityDataManager = entityDataManager; + m_Filter = default(ComponentGroupFilter); + } + + public void SetFilter(ArchetypeManager typeManager, ref ComponentGroupFilter filter) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + filter.AssertValid(); +#endif + var version = m_Filter.RequiredChangeVersion; + ResetFilter(typeManager); + m_Filter = filter; + m_Filter.RequiredChangeVersion = version; + } + + public void SetFilterChangedRequiredVersion(uint requiredVersion) + { + m_Filter.RequiredChangeVersion = requiredVersion; + } + + internal void ResetFilter(ArchetypeManager typeManager) + { + if (m_Filter.Type == FilterType.SharedComponent) + { + var filteredCount = m_Filter.Shared.Count; + + fixed (int* sharedComponentIndexPtr = m_Filter.Shared.SharedComponentIndex) + { + for (var i = 0; i < filteredCount; ++i) + typeManager.GetSharedComponentDataManager().RemoveReference(sharedComponentIndexPtr[i]); + } + } + + m_Filter.Type = FilterType.None; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public AtomicSafetyHandle GetSafetyHandle(ComponentJobSafetyManager safetyManager, int indexInComponentGroup) + { + var type = m_GroupData->RequiredComponents + indexInComponentGroup; + var isReadOnly = type->AccessModeType == ComponentType.AccessMode.ReadOnly; + return safetyManager.GetSafetyHandle(type->TypeIndex, isReadOnly); + } +#endif + + public bool GetIsReadOnly(int indexInComponentGroup) + { + var type = m_GroupData->RequiredComponents + indexInComponentGroup; + var isReadOnly = type->AccessModeType == ComponentType.AccessMode.ReadOnly; + return isReadOnly; + } + + + public bool IsEmptyIgnoreFilter + { + get + { + for (var match = m_GroupData->FirstMatchingArchetype; match != null; match = match->Next) + if (match->Archetype->EntityCount > 0) + return false; + + return true; + } + } + + internal void GetComponentChunkIterator(out int outLength, out ComponentChunkIterator outIterator) + { + outLength = ComponentChunkIterator.CalculateLength(m_GroupData->FirstMatchingArchetype, ref m_Filter); + outIterator = new ComponentChunkIterator(m_GroupData->FirstMatchingArchetype, + m_EntityDataManager->GlobalSystemVersion, ref m_Filter); + } + + internal void GetComponentChunkIterators(ForEachComponentGroupFilter forEachFilter) + { + var numFilters = forEachFilter.SharedComponentIndex.Length; + + var firstArchetype = new NativeArray(numFilters, Allocator.Temp); + var firstNonEmptyChunk = new NativeArray(numFilters, Allocator.Temp); + + ComponentChunkIterator.CalculateInitialChunkIterators(m_GroupData->FirstMatchingArchetype, + forEachFilter.IndexInComponentGroup, forEachFilter.SharedComponentIndex, + firstArchetype, firstNonEmptyChunk, forEachFilter.ItemLength); + + for (var i = 0; i < numFilters; ++i) + { + var filter = new ComponentGroupFilter(); + filter.Type = FilterType.SharedComponent; + filter.Shared.Count = 1; + filter.Shared.IndexInComponentGroup[0] = forEachFilter.IndexInComponentGroup; + filter.Shared.SharedComponentIndex[0] = forEachFilter.SharedComponentIndex[i]; + + forEachFilter.ItemIterator[i] = new ComponentChunkIterator((MatchingArchetypes*) firstArchetype[i], + m_EntityDataManager->GlobalSystemVersion, ref filter); + } + + firstArchetype.Dispose(); + firstNonEmptyChunk.Dispose(); + } + + internal int GetIndexInComponentGroup(int componentType) + { + var componentIndex = 0; + while (componentIndex < m_GroupData->RequiredComponentsCount && + m_GroupData->RequiredComponents[componentIndex].TypeIndex != componentType) + ++componentIndex; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (componentIndex >= m_GroupData->RequiredComponentsCount) + throw new InvalidOperationException( + $"Trying to get iterator for {TypeManager.GetType(componentType)} but the required component type was not declared in the EntityGroup."); +#endif + return componentIndex; + } + + internal int ComponentTypeIndex(int indexInComponentGroup) + { + return m_GroupData->RequiredComponents[indexInComponentGroup].TypeIndex; + } + + public bool CompareComponents(ComponentType[] componentTypes) + { + fixed (ComponentType* ptr = componentTypes) + { + return CompareComponents(ptr, componentTypes.Length); + } + } + + internal bool CompareComponents(ComponentType* componentTypes, int count) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (var k = 0; k < count; ++k) + if (componentTypes[k].TypeIndex == TypeManager.GetTypeIndex()) + throw new ArgumentException( + "ComponentGroup.CompareComponents may not include typeof(Entity), it is implicit"); +#endif + + // ComponentGroups are constructed including the Entity ID + var requiredCount = m_GroupData->RequiredComponentsCount; + if (count != requiredCount - 1) + return false; + + for (var k = 0; k < count; ++k) + { + int i; + for (i = 1; i < requiredCount; ++i) + if (m_GroupData->RequiredComponents[i] == componentTypes[k]) + break; + + if (i == requiredCount) + return false; + } + + return true; + } + + public ComponentType[] Types + { + get + { + var types = new List(); + for (var i = 0; i < m_GroupData->RequiredComponentsCount; ++i) + types.Add(m_GroupData->RequiredComponents[i]); + + return types.ToArray(); + } + } + + public int GetCombinedComponentOrderVersion() + { + var version = 0; + + for (var i = 0; i < m_GroupData->RequiredComponentsCount; ++i) + version += m_EntityDataManager->GetComponentTypeOrderVersion(m_GroupData->RequiredComponents[i] + .TypeIndex); + + return version; + } + + + internal void CompleteDependency(ComponentJobSafetyManager safetyManager) + { + safetyManager.CompleteDependencies(m_GroupData->ReaderTypes, m_GroupData->ReaderTypesCount, + m_GroupData->WriterTypes, m_GroupData->WriterTypesCount); + } + + internal JobHandle GetDependency(ComponentJobSafetyManager safetyManager) + { + return safetyManager.GetDependency(m_GroupData->ReaderTypes, m_GroupData->ReaderTypesCount, + m_GroupData->WriterTypes, m_GroupData->WriterTypesCount); + } + + internal void AddDependency(ComponentJobSafetyManager safetyManager, JobHandle job) + { + safetyManager.AddDependency(m_GroupData->ReaderTypes, m_GroupData->ReaderTypesCount, + m_GroupData->WriterTypes, m_GroupData->WriterTypesCount, job); + } + + internal int CalculateNumberOfChunksWithoutFiltering() + { + return ComponentChunkIterator.CalculateNumberOfChunksWithoutFiltering(m_GroupData->FirstMatchingArchetype); + } + } + + public unsafe class ComponentGroup : IDisposable + { + private readonly ComponentJobSafetyManager m_SafetyManager; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal string DisallowDisposing = null; +#endif + + // TODO: this is temporary, used to cache some state to avoid recomputing the TransformAccessArray. We need to improve this. + internal IDisposable m_CachedState; + private ComponentGroupData m_ComponentGroupData; + + internal ComponentGroup(EntityGroupData* groupData, ComponentJobSafetyManager safetyManager, + ArchetypeManager typeManager, EntityDataManager* entityDataManager) + { + m_ComponentGroupData = new ComponentGroupData(groupData, entityDataManager); + m_SafetyManager = safetyManager; + ArchetypeManager = typeManager; + EntityDataManager = entityDataManager; + } + + internal EntityDataManager* EntityDataManager { get; } + + public bool IsEmptyIgnoreFilter => m_ComponentGroupData.IsEmptyIgnoreFilter; + + public ComponentType[] Types => m_ComponentGroupData.Types; + + internal ArchetypeManager ArchetypeManager { get; } + + public void Dispose() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (DisallowDisposing != null) + throw new ArgumentException(DisallowDisposing); +#endif + + if (m_CachedState != null) + m_CachedState.Dispose(); + + m_ComponentGroupData.ResetFilter(ArchetypeManager); + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal AtomicSafetyHandle GetSafetyHandle(int indexInComponentGroup) + { + return m_ComponentGroupData.GetSafetyHandle(m_SafetyManager, indexInComponentGroup); + } +#endif + + internal void GetComponentChunkIterator(out int length, out ComponentChunkIterator iterator) + { + m_ComponentGroupData.GetComponentChunkIterator(out length, out iterator); + } + + internal int GetIndexInComponentGroup(int componentType) + { + return m_ComponentGroupData.GetIndexInComponentGroup(componentType); + } + + internal void GetComponentDataArray(ref ComponentChunkIterator iterator, int indexInComponentGroup, + int length, out ComponentDataArray output) where T : struct, IComponentData + { + iterator.IndexInComponentGroup = indexInComponentGroup; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + output = new ComponentDataArray(iterator, length, GetSafetyHandle(indexInComponentGroup)); +#else + output = new ComponentDataArray(iterator, length); +#endif + } + + public ComponentDataArray GetComponentDataArray() where T : struct, IComponentData + { + int length; + ComponentChunkIterator iterator; + GetComponentChunkIterator(out length, out iterator); + var indexInComponentGroup = GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + + ComponentDataArray res; + GetComponentDataArray(ref iterator, indexInComponentGroup, length, out res); + return res; + } + + public ComponentDataArray GetComponentDataArray(ForEachComponentGroupFilter filter, int filterIdx) + where T : struct, IComponentData + { + var indexInComponentGroup = GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + + ComponentDataArray res; + var iterator = filter.ItemIterator[filterIdx]; + GetComponentDataArray(ref iterator, indexInComponentGroup, filter.ItemLength[filterIdx], out res); + return res; + } + + internal void GetSharedComponentDataArray(ref ComponentChunkIterator iterator, int indexInComponentGroup, + int length, out SharedComponentDataArray output) where T : struct, ISharedComponentData + { + iterator.IndexInComponentGroup = indexInComponentGroup; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var typeIndex = m_ComponentGroupData.ComponentTypeIndex(indexInComponentGroup); + output = new SharedComponentDataArray(ArchetypeManager.GetSharedComponentDataManager(), + indexInComponentGroup, iterator, length, m_SafetyManager.GetSafetyHandle(typeIndex, true)); +#else + output = new SharedComponentDataArray(ArchetypeManager.GetSharedComponentDataManager(), + indexInComponentGroup, iterator, length); +#endif + } + + public SharedComponentDataArray GetSharedComponentDataArray() where T : struct, ISharedComponentData + { + int length; + ComponentChunkIterator iterator; + GetComponentChunkIterator(out length, out iterator); + var indexInComponentGroup = GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + + SharedComponentDataArray res; + GetSharedComponentDataArray(ref iterator, indexInComponentGroup, length, out res); + return res; + } + + internal void GetFixedArrayArray(ref ComponentChunkIterator iterator, int indexInComponentGroup, int length, + out FixedArrayArray output) where T : struct + { + iterator.IndexInComponentGroup = indexInComponentGroup; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + output = new FixedArrayArray(iterator, length, m_ComponentGroupData.GetIsReadOnly(indexInComponentGroup), + GetSafetyHandle(indexInComponentGroup)); +#else + output = + new FixedArrayArray(iterator, length, m_ComponentGroupData.GetIsReadOnly(indexInComponentGroup)); +#endif + } + + public FixedArrayArray GetFixedArrayArray() where T : struct + { + int length; + ComponentChunkIterator iterator; + GetComponentChunkIterator(out length, out iterator); + var indexInComponentGroup = GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + + FixedArrayArray res; + GetFixedArrayArray(ref iterator, indexInComponentGroup, length, out res); + return res; + } + + internal void GetEntityArray(ref ComponentChunkIterator iterator, int length, out EntityArray output) + { + iterator.IndexInComponentGroup = 0; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + output = new EntityArray(iterator, length, + m_SafetyManager.GetSafetyHandle(TypeManager.GetTypeIndex(), true)); +#else + output = new EntityArray(iterator, length); +#endif + } + + public EntityArray GetEntityArray() + { + int length; + ComponentChunkIterator iterator; + GetComponentChunkIterator(out length, out iterator); + + EntityArray res; + GetEntityArray(ref iterator, length, out res); + return res; + } + + public EntityArray GetEntityArray(ForEachComponentGroupFilter filter, int filterIdx) + { + EntityArray res; + var iterator = filter.ItemIterator[filterIdx]; + GetEntityArray(ref iterator, filter.ItemLength[filterIdx], out res); + return res; + } + + public int CalculateLength() + { + int length; + ComponentChunkIterator iterator; + GetComponentChunkIterator(out length, out iterator); + return length; + } + + public bool CompareComponents(ComponentType[] componentTypes) + { + return m_ComponentGroupData.CompareComponents(componentTypes); + } + + internal bool CompareComponents(ComponentType* componentTypes, int count) + { + return m_ComponentGroupData.CompareComponents(componentTypes, count); + } + + public void ResetFilter() + { + m_ComponentGroupData.ResetFilter(ArchetypeManager); + } + + public void SetFilter(SharedComponent1 sharedComponent1) + where SharedComponent1 : struct, ISharedComponentData + { + var filter = new ComponentGroupFilter(); + filter.Type = FilterType.SharedComponent; + filter.Shared.Count = 1; + filter.Shared.IndexInComponentGroup[0] = + GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + filter.Shared.SharedComponentIndex[0] = ArchetypeManager.GetSharedComponentDataManager() + .InsertSharedComponent(sharedComponent1); + + m_ComponentGroupData.SetFilter(ArchetypeManager, ref filter); + } + + public ForEachComponentGroupFilter CreateForEachFilter( + List sharedComponent1) + where SharedComponent1 : struct, ISharedComponentData + { + var forEachFilter = new ForEachComponentGroupFilter(); + forEachFilter.TypeManager = ArchetypeManager; + forEachFilter.ItemIterator = + new NativeArray(sharedComponent1.Count, Allocator.Temp); + forEachFilter.ItemLength = new NativeArray(sharedComponent1.Count, Allocator.Temp); + forEachFilter.SharedComponentIndex = new NativeArray(sharedComponent1.Count, Allocator.Temp); + forEachFilter.IndexInComponentGroup = + GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + for (var i = 0; i < sharedComponent1.Count; ++i) + forEachFilter.SharedComponentIndex[i] = ArchetypeManager.GetSharedComponentDataManager() + .InsertSharedComponent(sharedComponent1[i]); + + m_ComponentGroupData.GetComponentChunkIterators(forEachFilter); + + return forEachFilter; + } + + public void SetFilter(SharedComponent1 sharedComponent1, + SharedComponent2 sharedComponent2) + where SharedComponent1 : struct, ISharedComponentData + where SharedComponent2 : struct, ISharedComponentData + { + var filter = new ComponentGroupFilter(); + filter.Type = FilterType.SharedComponent; + filter.Shared.Count = 2; + filter.Shared.IndexInComponentGroup[0] = + GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + filter.Shared.SharedComponentIndex[0] = ArchetypeManager.GetSharedComponentDataManager() + .InsertSharedComponent(sharedComponent1); + + filter.Shared.IndexInComponentGroup[1] = + GetIndexInComponentGroup(TypeManager.GetTypeIndex()); + filter.Shared.SharedComponentIndex[1] = ArchetypeManager.GetSharedComponentDataManager() + .InsertSharedComponent(sharedComponent2); + + m_ComponentGroupData.SetFilter(ArchetypeManager, ref filter); + } + + public void SetFilterChanged(ComponentType componentType) + { + var filter = new ComponentGroupFilter(); + filter.Type = FilterType.Changed; + filter.Changed.Count = 1; + filter.Changed.IndexInComponentGroup[0] = GetIndexInComponentGroup(componentType.TypeIndex); + + m_ComponentGroupData.SetFilter(ArchetypeManager, ref filter); + } + + public void SetFilterChangedRequiredVersion(uint requiredVersion) + { + m_ComponentGroupData.SetFilterChangedRequiredVersion(requiredVersion); + } + + public void SetFilterChanged(ComponentType[] componentType) + { + if (componentType.Length > ComponentGroupFilter.ChangedFilter.Capacity) + throw new ArgumentException( + $"ComponentGroup.SetFilterChanged accepts a maximum of {ComponentGroupFilter.ChangedFilter.Capacity} component array length"); + if (componentType.Length <= 0) + throw new ArgumentException( + $"ComponentGroup.SetFilterChanged component array length must be larger than 0"); + + var filter = new ComponentGroupFilter(); + filter.Type = FilterType.Changed; + filter.Changed.Count = componentType.Length; + for (var i = 0; i != componentType.Length; i++) + filter.Changed.IndexInComponentGroup[i] = GetIndexInComponentGroup(componentType[i].TypeIndex); + + m_ComponentGroupData.SetFilter(ArchetypeManager, ref filter); + } + + + public void SetFilter(NativeArray indexList) + { + var filter = new ComponentGroupFilter(); + filter.Type = FilterType.IndexList; + filter.Indices.Length = indexList.Length; + filter.Indices.Indices = (int*) indexList.GetUnsafeReadOnlyPtr(); + + m_ComponentGroupData.SetFilter(ArchetypeManager, ref filter); + } + + public void CompleteDependency() + { + m_ComponentGroupData.CompleteDependency(m_SafetyManager); + } + + public JobHandle GetDependency() + { + return m_ComponentGroupData.GetDependency(m_SafetyManager); + } + + public void AddDependency(JobHandle job) + { + m_ComponentGroupData.AddDependency(m_SafetyManager, job); + } + + public int GetCombinedComponentOrderVersion() + { + return m_ComponentGroupData.GetCombinedComponentOrderVersion(); + } + + internal ArchetypeManager GetArchetypeManager() + { + return ArchetypeManager; + } + + internal int CalculateNumberOfChunksWithoutFiltering() + { + return m_ComponentGroupData.CalculateNumberOfChunksWithoutFiltering(); + } + } +} diff --git a/Unity.Entities/Iterators/ComponentGroup.cs.meta b/Unity.Entities/Iterators/ComponentGroup.cs.meta new file mode 100644 index 00000000..08a41540 --- /dev/null +++ b/Unity.Entities/Iterators/ComponentGroup.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: e5e6e5c15d4a044a699fce82fa67186b +timeCreated: 1506266985 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/ComponentGroupArray.cs b/Unity.Entities/Iterators/ComponentGroupArray.cs new file mode 100644 index 00000000..311520dd --- /dev/null +++ b/Unity.Entities/Iterators/ComponentGroupArray.cs @@ -0,0 +1,423 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using Unity.Assertions; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + internal class ComponentGroupArrayStaticCache + { + public readonly Type CachedType; + internal readonly int ComponentCount; + internal readonly int ComponentDataCount; + internal readonly int[] ComponentFieldOffsets; + internal readonly ComponentGroup ComponentGroup; + + internal readonly ComponentType[] ComponentTypes; + internal readonly ComponentJobSafetyManager SafetyManager; + + public ComponentGroupArrayStaticCache(Type type, EntityManager entityManager, ComponentSystemBase system) + { + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + + var componentFieldOffsetsBuilder = new List(); + var componentTypesBuilder = new List(); + + var componentDataFieldOffsetsBuilder = new List(); + var componentDataTypesBuilder = new List(); + + var subtractiveComponentTypesBuilder = new List(); + + foreach (var field in fields) + { + var fieldType = field.FieldType; + var offset = UnsafeUtility.GetFieldOffset(field); + + if (fieldType.IsPointer) + { + var isReadOnly = field.GetCustomAttributes(typeof(ReadOnlyAttribute), true).Length != 0; + var accessMode = + isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite; + + var elementType = fieldType.GetElementType(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!typeof(IComponentData).IsAssignableFrom(elementType) && elementType != typeof(Entity)) + throw new ArgumentException( + $"{type}.{field.Name} is a pointer type but not a IComponentData. Only IComponentData or Entity may be a pointer type for enumeration."); +#endif + componentDataFieldOffsetsBuilder.Add(offset); + componentDataTypesBuilder.Add(new ComponentType(elementType, accessMode)); + } + else if (fieldType.IsSubclassOf(TypeManager.UnityEngineComponentType)) + { + componentFieldOffsetsBuilder.Add(offset); + componentTypesBuilder.Add(fieldType); + } + else if (fieldType.IsGenericType && + fieldType.GetGenericTypeDefinition() == typeof(SubtractiveComponent<>)) + { + subtractiveComponentTypesBuilder.Add(ComponentType.Subtractive(fieldType.GetGenericArguments()[0])); + } +#if ENABLE_UNITY_COLLECTIONS_CHECKS + else if (typeof(IComponentData).IsAssignableFrom(fieldType)) + { + throw new ArgumentException( + $"{type}.{field.Name} must be an unsafe pointer to the {fieldType}. Like this: {fieldType}* {field.Name};"); + } + else + { + throw new ArgumentException($"{type}.{field.Name} can not be used in a component enumerator"); + } +#endif + } +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (componentTypesBuilder.Count + componentDataTypesBuilder.Count > ComponentGroupArrayData.kMaxStream) + throw new ArgumentException( + $"{type} has too many component references. A ComponentGroup Array can have up to {ComponentGroupArrayData.kMaxStream}."); +#endif + + ComponentDataCount = componentDataTypesBuilder.Count; + ComponentCount = componentTypesBuilder.Count; + + componentDataTypesBuilder.AddRange(componentTypesBuilder); + componentDataTypesBuilder.AddRange(subtractiveComponentTypesBuilder); + ComponentTypes = componentDataTypesBuilder.ToArray(); + + componentDataFieldOffsetsBuilder.AddRange(componentFieldOffsetsBuilder); + ComponentFieldOffsets = componentDataFieldOffsetsBuilder.ToArray(); + + ComponentGroup = system.GetComponentGroupInternal(ComponentTypes); + SafetyManager = entityManager.ComponentJobSafetyManager; + + CachedType = type; + } + } + + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + internal unsafe struct ComponentGroupArrayData + { + public const int kMaxStream = 6; + + private struct ComponentGroupStream + { + public byte* CachedPtr; + public int SizeOf; + public ushort FieldOffset; + public ushort TypeIndexInArchetype; + } + + private fixed byte m_Caches[16 * kMaxStream]; + + private readonly int m_ComponentDataCount; + private readonly int m_ComponentCount; + + // The following 3 fields must not be renamed, unless JobReflectionData.cpp is changed accordingly. + // TODO: make JobDebugger logic more solid, either by using codegen proxies or attributes. + public readonly int m_Length; + public readonly int m_MinIndex; + public readonly int m_MaxIndex; + + public int CacheBeginIndex; + public int CacheEndIndex; + + private ComponentChunkIterator m_ChunkIterator; + private fixed int m_ComponentTypes[kMaxStream]; + private fixed bool m_IsWriting[kMaxStream]; + + // The following fields must not be renamed, unless JobReflectionData.cpp is changed accordingly. + // TODO: make JobDebugger logic more solid, either by using codegen proxies or attributes. +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly int m_SafetyReadOnlyCount; + private readonly int m_SafetyReadWriteCount; +#pragma warning disable 414 + private AtomicSafetyHandle m_Safety0; + private AtomicSafetyHandle m_Safety1; + private AtomicSafetyHandle m_Safety2; + private AtomicSafetyHandle m_Safety3; + private AtomicSafetyHandle m_Safety4; + private AtomicSafetyHandle m_Safety5; +#pragma warning restore +#endif + + [NativeSetClassTypeToNullOnSchedule] private readonly ArchetypeManager m_ArchetypeManager; + + public ComponentGroupArrayData(ComponentGroupArrayStaticCache staticCache) + { + var length = 0; + staticCache.ComponentGroup.GetComponentChunkIterator(out length, out m_ChunkIterator); + m_ChunkIterator.IndexInComponentGroup = 0; + + m_Length = length; + m_MinIndex = 0; + m_MaxIndex = length - 1; + + CacheBeginIndex = 0; + CacheEndIndex = 0; + m_ArchetypeManager = staticCache.ComponentGroup.GetArchetypeManager(); + + m_ComponentDataCount = staticCache.ComponentDataCount; + m_ComponentCount = staticCache.ComponentCount; + + fixed (int* componentTypes = m_ComponentTypes) + { + fixed (byte* cacheBytes = m_Caches) + { + fixed (bool* isWriting = m_IsWriting) + { + var streams = (ComponentGroupStream*) cacheBytes; + + for (var i = 0; i < staticCache.ComponentDataCount + staticCache.ComponentCount; i++) + { + componentTypes[i] = staticCache.ComponentTypes[i].TypeIndex; + streams[i].FieldOffset = (ushort) staticCache.ComponentFieldOffsets[i]; + isWriting[i] = staticCache.ComponentTypes[i].AccessModeType == + ComponentType.AccessMode.ReadWrite; + } + } + } + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_Safety0 = new AtomicSafetyHandle(); + m_Safety1 = new AtomicSafetyHandle(); + m_Safety2 = new AtomicSafetyHandle(); + m_Safety3 = new AtomicSafetyHandle(); + m_Safety4 = new AtomicSafetyHandle(); + m_Safety5 = new AtomicSafetyHandle(); + Assert.AreEqual(6, kMaxStream); + + m_SafetyReadWriteCount = 0; + m_SafetyReadOnlyCount = 0; + var safetyManager = staticCache.SafetyManager; + fixed (AtomicSafetyHandle* safety = &m_Safety0) + { + for (var i = 0; i != staticCache.ComponentTypes.Length; i++) + { + var type = staticCache.ComponentTypes[i]; + if (type.AccessModeType != ComponentType.AccessMode.ReadOnly) + continue; + + safety[m_SafetyReadOnlyCount] = safetyManager.GetSafetyHandle(type.TypeIndex, true); + m_SafetyReadOnlyCount++; + } + + for (var i = 0; i != staticCache.ComponentTypes.Length; i++) + { + var type = staticCache.ComponentTypes[i]; + if (type.AccessModeType != ComponentType.AccessMode.ReadWrite) + continue; + + safety[m_SafetyReadOnlyCount + m_SafetyReadWriteCount] = + safetyManager.GetSafetyHandle(type.TypeIndex, false); + m_SafetyReadWriteCount++; + } + } +#endif + } + + public void UpdateCache(int index) + { + ComponentChunkCache cache; + + m_ChunkIterator.UpdateCache(index, out cache, false); + + CacheBeginIndex = cache.CachedBeginIndex; + CacheEndIndex = cache.CachedEndIndex; + + fixed (int* componentTypes = m_ComponentTypes) + { + fixed (byte* cacheBytes = m_Caches) + { + fixed (bool* isWriting = m_IsWriting) + { + var streams = (ComponentGroupStream*) cacheBytes; + var totalCount = m_ComponentDataCount + m_ComponentCount; + for (var i = 0; i < totalCount; i++) + { + int indexInArcheType; + m_ChunkIterator.GetCacheForType(componentTypes[i], isWriting[i], out cache, + out indexInArcheType); + streams[i].SizeOf = cache.CachedSizeOf; + streams[i].CachedPtr = (byte*) cache.CachedPtr; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (indexInArcheType > ushort.MaxValue) + throw new ArgumentException( + $"There is a maximum of {ushort.MaxValue} components on one entity."); +#endif + streams[i].TypeIndexInArchetype = (ushort) indexInArcheType; + } + } + } + } + } + + public void CheckAccess() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + fixed (AtomicSafetyHandle* safety = &m_Safety0) + { + for (var i = 0; i < m_SafetyReadOnlyCount; i++) + AtomicSafetyHandle.CheckReadAndThrow(safety[i]); + + for (var i = m_SafetyReadOnlyCount; i < m_SafetyReadOnlyCount + m_SafetyReadWriteCount; i++) + AtomicSafetyHandle.CheckWriteAndThrow(safety[i]); + } +#endif + } + + public void PatchPtrs(int index, byte* valuePtr) + { + fixed (byte* cacheBytes = m_Caches) + { + var streams = (ComponentGroupStream*) cacheBytes; + for (var i = 0; i != m_ComponentDataCount; i++) + { + var componentPtr = (void*) (streams[i].CachedPtr + streams[i].SizeOf * index); + var valuePtrOffsetted = (void**) (valuePtr + streams[i].FieldOffset); + + *valuePtrOffsetted = componentPtr; + } + } + } + + [BurstDiscard] + public void PatchManagedPtrs(int index, byte* valuePtr) + { + fixed (byte* cacheBytes = m_Caches) + { + var streams = (ComponentGroupStream*) cacheBytes; + for (var i = m_ComponentDataCount; i != m_ComponentDataCount + m_ComponentCount; i++) + { + var component = m_ChunkIterator.GetManagedObject(m_ArchetypeManager, + streams[i].TypeIndexInArchetype, CacheBeginIndex, index); + var valuePtrOffsetted = valuePtr + streams[i].FieldOffset; + UnsafeUtility.CopyObjectAddressToPtr(component, valuePtrOffsetted); + } + } + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + [BurstDiscard] + public void FailOutOfRangeError(int index) + { + /* + //@TODO: Make error message utility and share with NativeArray... + if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1)) + throw new IndexOutOfRangeException(string.Format( + "Index {0} is out of restricted component group array range [{1}...{2}] in ReadWriteBuffer.\n" + + "ReadWriteBuffers are restricted to only read & write the element at the job index. " + + "You can use double buffering strategies to avoid race conditions due to " + + "reading & writing in parallel to the same elements from a job.", + index, m_MinIndex, m_MaxIndex)); +*/ + throw new IndexOutOfRangeException($"Index {index} is out of range of '{m_Length}' Length."); + } +#endif + } + + public struct ComponentGroupArray : IDisposable where T : struct + { + internal ComponentGroupArrayData m_Data; + + internal ComponentGroupArray(ComponentGroupArrayStaticCache cache) + { + m_Data = new ComponentGroupArrayData(cache); + } + + public void Dispose() + { + } + + public int Length => m_Data.m_Length; + + public unsafe T this[int index] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_Data.CheckAccess(); + if (index < m_Data.m_MinIndex || index > m_Data.m_MaxIndex) + m_Data.FailOutOfRangeError(index); +#endif + + if (index < m_Data.CacheBeginIndex || index >= m_Data.CacheEndIndex) + m_Data.UpdateCache(index); + + var value = default(T); + var valuePtr = (byte*) UnsafeUtility.AddressOf(ref value); + m_Data.PatchPtrs(index, valuePtr); + m_Data.PatchManagedPtrs(index, valuePtr); + return value; + } + } + + public ComponentGroupEnumerator GetEnumerator() + { + return new ComponentGroupEnumerator(m_Data); + } + + public unsafe struct ComponentGroupEnumerator : IEnumerator where U : struct + { + private ComponentGroupArrayData m_Data; + private int m_Index; + + internal ComponentGroupEnumerator(ComponentGroupArrayData arrayData) + { + m_Data = arrayData; + m_Index = -1; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + m_Index++; + + if (m_Index >= m_Data.CacheBeginIndex && m_Index < m_Data.CacheEndIndex) + return true; + + if (m_Index >= m_Data.m_Length) + return false; + + m_Data.CheckAccess(); + m_Data.UpdateCache(m_Index); + + return true; + } + + public void Reset() + { + m_Index = -1; + } + + public U Current + { + get + { + m_Data.CheckAccess(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_Index < m_Data.m_MinIndex || m_Index > m_Data.m_MaxIndex) + m_Data.FailOutOfRangeError(m_Index); +#endif + + var value = default(U); + var valuePtr = (byte*) UnsafeUtility.AddressOf(ref value); + m_Data.PatchPtrs(m_Index, valuePtr); + m_Data.PatchManagedPtrs(m_Index, valuePtr); + return value; + } + } + + object IEnumerator.Current => Current; + } + } +} diff --git a/Unity.Entities/Iterators/ComponentGroupArray.cs.meta b/Unity.Entities/Iterators/ComponentGroupArray.cs.meta new file mode 100644 index 00000000..e3bcd5e2 --- /dev/null +++ b/Unity.Entities/Iterators/ComponentGroupArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2149666441034bd2be2b403b9bc5dbd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/EntityArray.cs b/Unity.Entities/Iterators/EntityArray.cs new file mode 100644 index 00000000..95a3b666 --- /dev/null +++ b/Unity.Entities/Iterators/EntityArray.cs @@ -0,0 +1,113 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + public unsafe struct EntityArray + { + private ComponentChunkIterator m_Iterator; + private ComponentChunkCache m_Cache; + + private readonly int m_Length; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly int m_MinIndex; + private readonly int m_MaxIndex; + private readonly AtomicSafetyHandle m_Safety; +#endif + public int Length => m_Length; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal EntityArray(ComponentChunkIterator iterator, int length, AtomicSafetyHandle safety) +#else + internal unsafe EntityArray(ComponentChunkIterator iterator, int length) +#endif + { + m_Length = length; + m_Iterator = iterator; + m_Cache = default(ComponentChunkCache); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_MinIndex = 0; + m_MaxIndex = length - 1; + m_Safety = safety; +#endif + } + + public Entity this[int index] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + if (index < m_MinIndex || index > m_MaxIndex) + FailOutOfRangeError(index); +#endif + + if (index < m_Cache.CachedBeginIndex || index >= m_Cache.CachedEndIndex) + m_Iterator.UpdateCache(index, out m_Cache, false); + + return UnsafeUtility.ReadArrayElement(m_Cache.CachedPtr, index); + } + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private void FailOutOfRangeError(int index) + { + //@TODO: Make error message utility and share with NativeArray... + if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1)) + throw new IndexOutOfRangeException( + $"Index {index} is out of restricted IJobParallelFor range [{m_MinIndex}...{m_MaxIndex}] in ReadWriteBuffer.\nReadWriteBuffers are restricted to only read & write the element at the job index. You can use double buffering strategies to avoid race conditions due to reading & writing in parallel to the same elements from a job."); + + throw new IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length."); + } +#endif + + public NativeArray GetChunkArray(int startIndex, int maxCount) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + + if (startIndex < m_MinIndex) + FailOutOfRangeError(startIndex); + else if (startIndex + maxCount > m_MaxIndex + 1) + FailOutOfRangeError(startIndex + maxCount); +#endif + + m_Iterator.UpdateCache(startIndex, out m_Cache, false); + + void* ptr = (byte*) m_Cache.CachedPtr + startIndex * m_Cache.CachedSizeOf; + var count = Math.Min(maxCount, m_Cache.CachedEndIndex - startIndex); + + var arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, count, Allocator.Invalid); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, m_Safety); +#endif + + return arr; + } + + public void CopyTo(NativeSlice dst, int startIndex = 0) + { + var copiedCount = 0; + while (copiedCount < dst.Length) + { + var chunkArray = GetChunkArray(startIndex + copiedCount, dst.Length - copiedCount); + dst.Slice(copiedCount, chunkArray.Length).CopyFrom(chunkArray); + + copiedCount += chunkArray.Length; + } + } + + public Entity[] ToArray() + { + var array = new Entity[Length]; + for (var i = 0; i != array.Length; i++) + array[i] = this[i]; + return array; + } + } +} diff --git a/Unity.Entities/Iterators/EntityArray.cs.meta b/Unity.Entities/Iterators/EntityArray.cs.meta new file mode 100644 index 00000000..3c408a56 --- /dev/null +++ b/Unity.Entities/Iterators/EntityArray.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 5e10ee0838de946aca61081698f37214 +timeCreated: 1504806398 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/EntityGroupManager.cs b/Unity.Entities/Iterators/EntityGroupManager.cs new file mode 100644 index 00000000..4befdca3 --- /dev/null +++ b/Unity.Entities/Iterators/EntityGroupManager.cs @@ -0,0 +1,225 @@ +using System; +using System.Runtime.InteropServices; +using Unity.Collections; +using UnityEngine.Assertions; + +namespace Unity.Entities +{ + internal unsafe class EntityGroupManager : IDisposable + { + private readonly ComponentJobSafetyManager m_JobSafetyManager; + private ChunkAllocator m_GroupDataChunkAllocator; + private NativeMultiHashMap m_GroupLookup; + private EntityGroupData* m_LastGroupData; + + public EntityGroupManager(ComponentJobSafetyManager safetyManager) + { + m_GroupLookup = new NativeMultiHashMap(256, Allocator.Persistent); + m_JobSafetyManager = safetyManager; + } + + public void Dispose() + { + //@TODO: Need to wait for all job handles to be completed.. + + m_GroupLookup.Dispose(); + m_GroupDataChunkAllocator.Dispose(); + } + + private EntityGroupData* GetCachedGroupData(uint hash, ComponentType* requiredTypes, + int requiredCount) + { + NativeMultiHashMapIterator it; + IntPtr grpPtr; + if (!m_GroupLookup.TryGetFirstValue(hash, out grpPtr, out it)) + return null; + do + { + var grp = (EntityGroupData*) grpPtr; + if (ComponentType.CompareArray(grp->RequiredComponents, grp->RequiredComponentsCount, requiredTypes, + requiredCount)) + return grp; + } while (m_GroupLookup.TryGetNextValue(out grpPtr, ref it)); + + return null; + } + + public ComponentGroup CreateEntityGroupIfCached(ArchetypeManager typeMan, EntityDataManager* entityDataManager, + ComponentType* requiredTypes, + int requiredCount) + { + var hash = HashUtility.Fletcher32((ushort*) requiredTypes, + requiredCount * sizeof(ComponentType) / sizeof(short)); + var grp = GetCachedGroupData(hash, requiredTypes, requiredCount); + if (grp != null) + return new ComponentGroup(grp, m_JobSafetyManager, typeMan, entityDataManager); + return null; + } + + public ComponentGroup CreateEntityGroup(ArchetypeManager typeMan, EntityDataManager* entityDataManager, + ComponentType* requiredTypes, int requiredCount) + { + var hash = HashUtility.Fletcher32((ushort*) requiredTypes, + requiredCount * sizeof(ComponentType) / sizeof(short)); + var grp = GetCachedGroupData(hash, requiredTypes, requiredCount); + if (grp != null) + return new ComponentGroup(grp, m_JobSafetyManager, typeMan, entityDataManager); + + m_JobSafetyManager.CompleteAllJobsAndInvalidateArrays(); + + grp = (EntityGroupData*) m_GroupDataChunkAllocator.Allocate(sizeof(EntityGroupData), 8); + grp->PrevGroup = m_LastGroupData; + m_LastGroupData = grp; + grp->RequiredComponentsCount = requiredCount; + grp->RequiredComponents = + (ComponentType*) m_GroupDataChunkAllocator.Construct(sizeof(ComponentType) * requiredCount, 4, + requiredTypes); + + grp->ReaderTypesCount = 0; + grp->WriterTypesCount = 0; + + grp->SubtractiveComponentsCount = 0; + + for (var i = 0; i != requiredCount; i++) + { + if (requiredTypes[i].AccessModeType == ComponentType.AccessMode.Subtractive) + grp->SubtractiveComponentsCount++; + if (!requiredTypes[i].RequiresJobDependency) + continue; + switch (requiredTypes[i].AccessModeType) + { + case ComponentType.AccessMode.ReadOnly: + grp->ReaderTypesCount++; + break; + default: + grp->WriterTypesCount++; + break; + } + } + + grp->ReaderTypes = (int*) m_GroupDataChunkAllocator.Allocate(sizeof(int) * grp->ReaderTypesCount, 4); + grp->WriterTypes = (int*) m_GroupDataChunkAllocator.Allocate(sizeof(int) * grp->WriterTypesCount, 4); + + var curReader = 0; + var curWriter = 0; + for (var i = 0; i != requiredCount; i++) + { + if (!requiredTypes[i].RequiresJobDependency) + continue; + switch (requiredTypes[i].AccessModeType) + { + case ComponentType.AccessMode.ReadOnly: + grp->ReaderTypes[curReader++] = requiredTypes[i].TypeIndex; + break; + default: + grp->WriterTypes[curWriter++] = requiredTypes[i].TypeIndex; + break; + } + } + + grp->RequiredComponents = + (ComponentType*) m_GroupDataChunkAllocator.Construct(sizeof(ComponentType) * requiredCount, 4, + requiredTypes); + + grp->FirstMatchingArchetype = null; + grp->LastMatchingArchetype = null; + for (var type = typeMan.m_LastArchetype; type != null; type = type->PrevArchetype) + AddArchetypeIfMatching(type, grp); + m_GroupLookup.Add(hash, (IntPtr) grp); + return new ComponentGroup(grp, m_JobSafetyManager, typeMan, entityDataManager); + } + + internal void OnArchetypeAdded(Archetype* type) + { + for (var grp = m_LastGroupData; grp != null; grp = grp->PrevGroup) + AddArchetypeIfMatching(type, grp); + } + + private void AddArchetypeIfMatching(Archetype* archetype, EntityGroupData* group) + { + // If the group has more actually required types than the archetype it can never match, so early out as an optimization + if (group->RequiredComponentsCount - group->SubtractiveComponentsCount > archetype->TypesCount) + return; + var typeI = 0; + var prevTypeI = 0; + for (var i = 0; i < group->RequiredComponentsCount; ++i, ++typeI) + { + while (archetype->Types[typeI].TypeIndex < group->RequiredComponents[i].TypeIndex && + typeI < archetype->TypesCount) + ++typeI; + + var hasComponent = !(typeI >= archetype->TypesCount); + + // Type mismatch + if (hasComponent && archetype->Types[typeI].TypeIndex != group->RequiredComponents[i].TypeIndex) + hasComponent = false; + + if (hasComponent && group->RequiredComponents[i].AccessModeType == ComponentType.AccessMode.Subtractive) + return; + if (!hasComponent && + group->RequiredComponents[i].AccessModeType != ComponentType.AccessMode.Subtractive) + return; + if (hasComponent) + prevTypeI = typeI; + else + typeI = prevTypeI; + } + + var match = (MatchingArchetypes*) m_GroupDataChunkAllocator.Allocate( + MatchingArchetypes.GetAllocationSize(group->RequiredComponentsCount), 8); + match->Archetype = archetype; + var typeIndexInArchetypeArray = match->TypeIndexInArchetypeArray; + + if (group->LastMatchingArchetype == null) + group->LastMatchingArchetype = match; + + match->Next = group->FirstMatchingArchetype; + group->FirstMatchingArchetype = match; + + for (var component = 0; component < group->RequiredComponentsCount; ++component) + { + var typeComponentIndex = -1; + if (group->RequiredComponents[component].AccessModeType != ComponentType.AccessMode.Subtractive) + { + typeComponentIndex = + ChunkDataUtility.GetIndexInTypeArray(archetype, group->RequiredComponents[component].TypeIndex); + Assert.AreNotEqual(-1, typeComponentIndex); + } + + typeIndexInArchetypeArray[component] = typeComponentIndex; + } + } + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct MatchingArchetypes + { + public Archetype* Archetype; + + public MatchingArchetypes* Next; + + //@TODO: Rename to IndexInArchetype + public fixed int TypeIndexInArchetypeArray[1]; + + public static int GetAllocationSize(int requiredComponentsCount) + { + return sizeof(MatchingArchetypes) + sizeof(int) * (requiredComponentsCount - 1); + } + } + + internal unsafe struct EntityGroupData + { + public int* ReaderTypes; + public int ReaderTypesCount; + + public int* WriterTypes; + public int WriterTypesCount; + + public ComponentType* RequiredComponents; + public int RequiredComponentsCount; + public int SubtractiveComponentsCount; + public MatchingArchetypes* FirstMatchingArchetype; + public MatchingArchetypes* LastMatchingArchetype; + public EntityGroupData* PrevGroup; + } +} diff --git a/Unity.Entities/Iterators/EntityGroupManager.cs.meta b/Unity.Entities/Iterators/EntityGroupManager.cs.meta new file mode 100644 index 00000000..89584494 --- /dev/null +++ b/Unity.Entities/Iterators/EntityGroupManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a013b8fb8841746a2aacbf04b7e3d254 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/FixedArrayArray.cs b/Unity.Entities/Iterators/FixedArrayArray.cs new file mode 100644 index 00000000..2f454ee7 --- /dev/null +++ b/Unity.Entities/Iterators/FixedArrayArray.cs @@ -0,0 +1,88 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + public unsafe struct FixedArrayArray where T : struct + { + private ComponentChunkCache m_Cache; + private ComponentChunkIterator m_Iterator; + private int m_CachedFixedArrayLength; + private readonly bool m_IsReadOnly; + + + private readonly int m_Length; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly int m_MinIndex; + private readonly int m_MaxIndex; + private readonly AtomicSafetyHandle m_Safety; +#endif + public int Length => m_Length; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal FixedArrayArray(ComponentChunkIterator iterator, int length, bool isReadOnly, + AtomicSafetyHandle safety) +#else + internal FixedArrayArray(ComponentChunkIterator iterator, int length, bool isReadOnly) +#endif + { + m_Length = length; + m_IsReadOnly = isReadOnly; + m_Iterator = iterator; + m_Cache = default(ComponentChunkCache); + m_CachedFixedArrayLength = -1; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_MinIndex = 0; + m_MaxIndex = length - 1; + m_Safety = safety; +#endif + } + + public NativeArray this[int index] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + if (index < m_MinIndex || index > m_MaxIndex) + FailOutOfRangeError(index); + var safety = m_Safety; +#endif + + if (index < m_Cache.CachedBeginIndex || index >= m_Cache.CachedEndIndex) + { + m_Iterator.UpdateCache(index, out m_Cache, !m_IsReadOnly); + m_CachedFixedArrayLength = m_Cache.CachedSizeOf / UnsafeUtility.SizeOf(); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_Cache.CachedSizeOf % UnsafeUtility.SizeOf() != 0) + throw new InvalidOperationException("Fixed array size must be multiple of sizeof"); +#endif + } + + void* ptr = (byte*) m_Cache.CachedPtr + index * m_Cache.CachedSizeOf; + var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, m_CachedFixedArrayLength, + Allocator.Invalid); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, safety); +#endif + return array; + } + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private void FailOutOfRangeError(int index) + { + //@TODO: Make error message utility and share with NativeArray... + if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1)) + throw new IndexOutOfRangeException( + $"Index {index} is out of restricted IJobParallelFor range [{m_MinIndex}...{m_MaxIndex}] in ReadWriteBuffer.\nReadWriteBuffers are restricted to only read & write the element at the job index. You can use double buffering strategies to avoid race conditions due to reading & writing in parallel to the same elements from a job."); + + throw new IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length."); + } +#endif + } +} diff --git a/Unity.Entities/Iterators/FixedArrayArray.cs.meta b/Unity.Entities/Iterators/FixedArrayArray.cs.meta new file mode 100644 index 00000000..3e91ca74 --- /dev/null +++ b/Unity.Entities/Iterators/FixedArrayArray.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 24ee6c53bdd9546d0982af7d8b40e84d +timeCreated: 1504713176 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Iterators/FixedArrayFromEntity.cs b/Unity.Entities/Iterators/FixedArrayFromEntity.cs new file mode 100644 index 00000000..22775400 --- /dev/null +++ b/Unity.Entities/Iterators/FixedArrayFromEntity.cs @@ -0,0 +1,77 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + [NativeContainer] + public unsafe struct FixedArrayFromEntity where T : struct + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly AtomicSafetyHandle m_Safety; +#endif + [NativeDisableUnsafePtrRestriction] private readonly EntityDataManager* m_Entities; + private readonly int m_TypeIndex; + private readonly bool m_IsReadOnly; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal FixedArrayFromEntity(int typeIndex, EntityDataManager* entityData, bool isReadOnly, + AtomicSafetyHandle safety) + { + m_Safety = safety; + m_TypeIndex = typeIndex; + m_Entities = entityData; + m_IsReadOnly = isReadOnly; + + if (TypeManager.GetComponentType(m_TypeIndex).Category != TypeManager.TypeCategory.OtherValueType) + throw new ArgumentException( + $"GetComponentFixedArray<{typeof(T)}> may not be IComponentData or ISharedComponentData"); + } +#else + internal FixedArrayFromEntity(int typeIndex, EntityDataManager* entityData, bool isReadOnly) + { + m_TypeIndex = typeIndex; + m_Entities = entityData; + m_IsReadOnly = isReadOnly; + } +#endif + + public bool Exists(Entity entity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); +#endif + //@TODO: out of bounds index checks... + + return m_Entities->HasComponent(entity, m_TypeIndex); + } + + public NativeArray this[Entity entity] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Note that this check is only for the lookup table into the entity manager + // The native array performs the actual read only / write only checks + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + + m_Entities->AssertEntityHasComponent(entity, m_TypeIndex); +#endif + + byte* ptr; + int length; + m_Entities->GetComponentDataWithTypeAndFixedArrayLength(entity, m_TypeIndex, out ptr, out length, + !m_IsReadOnly); + + var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, length, + Allocator.Invalid); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, m_Safety); +#endif + + return array; + } + } + } +} diff --git a/Unity.Entities/Iterators/FixedArrayFromEntity.cs.meta b/Unity.Entities/Iterators/FixedArrayFromEntity.cs.meta new file mode 100644 index 00000000..ffa888fe --- /dev/null +++ b/Unity.Entities/Iterators/FixedArrayFromEntity.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4c8e2b5c8e7f4feb920331cce4ddb30d +timeCreated: 1511024155 \ No newline at end of file diff --git a/Unity.Entities/Iterators/SharedComponentDataArray.cs b/Unity.Entities/Iterators/SharedComponentDataArray.cs new file mode 100644 index 00000000..f1a96ad9 --- /dev/null +++ b/Unity.Entities/Iterators/SharedComponentDataArray.cs @@ -0,0 +1,62 @@ +using System; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities +{ + public struct SharedComponentDataArray where T : struct, ISharedComponentData + { + private ComponentChunkIterator m_Iterator; + private ComponentChunkCache m_Cache; + private readonly SharedComponentDataManager m_sharedComponentDataManager; + private readonly int m_sharedComponentIndex; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly AtomicSafetyHandle m_Safety; +#endif + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal SharedComponentDataArray(SharedComponentDataManager sharedComponentDataManager, + int sharedComponentIndex, ComponentChunkIterator iterator, int length, AtomicSafetyHandle safety) +#else + internal unsafe SharedComponentDataArray(SharedComponentDataManager sharedComponentDataManager, int sharedComponentIndex, ComponentChunkIterator iterator, int length) +#endif + { + m_sharedComponentDataManager = sharedComponentDataManager; + m_sharedComponentIndex = sharedComponentIndex; + m_Iterator = iterator; + m_Cache = default(ComponentChunkCache); + + Length = length; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_Safety = safety; +#endif + } + + public T this[int index] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + if ((uint) index >= (uint) Length) + FailOutOfRangeError(index); +#endif + + if (index < m_Cache.CachedBeginIndex || index >= m_Cache.CachedEndIndex) + m_Iterator.UpdateCache(index, out m_Cache, false); + + var sharedComponent = m_Iterator.GetSharedComponentFromCurrentChunk(m_sharedComponentIndex); + return m_sharedComponentDataManager.GetSharedComponentData(sharedComponent); + } + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private void FailOutOfRangeError(int index) + { + throw new IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length."); + } +#endif + + public int Length { get; } + } +} diff --git a/Unity.Entities/Iterators/SharedComponentDataArray.cs.meta b/Unity.Entities/Iterators/SharedComponentDataArray.cs.meta new file mode 100644 index 00000000..98380507 --- /dev/null +++ b/Unity.Entities/Iterators/SharedComponentDataArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36d1b790446ccf84e8be1845686c0c98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/SceneViewWorldPositionAttribute.cs b/Unity.Entities/SceneViewWorldPositionAttribute.cs new file mode 100644 index 00000000..97bc8b35 --- /dev/null +++ b/Unity.Entities/SceneViewWorldPositionAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace Unity.Entities +{ + public class SceneViewWorldPositionAttribute : Attribute + { + } +} diff --git a/Unity.Entities/SceneViewWorldPositionAttribute.cs.meta b/Unity.Entities/SceneViewWorldPositionAttribute.cs.meta new file mode 100644 index 00000000..94350cb0 --- /dev/null +++ b/Unity.Entities/SceneViewWorldPositionAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6b033f78fccd4c7187ca119879f3c75 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ScriptBehaviourManager.cs b/Unity.Entities/ScriptBehaviourManager.cs new file mode 100644 index 00000000..35e7f13f --- /dev/null +++ b/Unity.Entities/ScriptBehaviourManager.cs @@ -0,0 +1,89 @@ +using System; +#if UNITY_EDITOR +using UnityEngine.Profiling; + +#endif + +namespace Unity.Entities +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class DisableAutoCreationAttribute : Attribute + { + } + + public abstract class ScriptBehaviourManager + { +#if UNITY_EDITOR + private CustomSampler sampler; +#endif + internal void CreateInstance(World world, int capacity) + { + OnBeforeCreateManagerInternal(world, capacity); + try + { + OnCreateManager(capacity); +#if UNITY_EDITOR + var type = GetType(); + sampler = CustomSampler.Create($"{world.Name} {type.FullName}"); +#endif + } + catch + { + OnBeforeDestroyManagerInternal(); + OnAfterDestroyManagerInternal(); + throw; + } + } + + internal void DestroyInstance() + { + OnBeforeDestroyManagerInternal(); + OnDestroyManager(); + OnAfterDestroyManagerInternal(); + } + + protected abstract void OnBeforeCreateManagerInternal(World world, int capacity); + + protected abstract void OnBeforeDestroyManagerInternal(); + protected abstract void OnAfterDestroyManagerInternal(); + + /// + /// Called when the ScriptBehaviourManager is created. + /// When a new domain is loaded, OnCreate on the necessary manager will be invoked + /// before the ScriptBehaviour will receive its first OnCreate() call. + /// capacity can be configured in Edit -> Configure Memory + /// + /// + /// Capacity describes how many objects will register with the manager. This lets you reduce realloc + /// calls while the game is running. + /// + protected virtual void OnCreateManager(int capacity) + { + } + + /// + /// Called when the ScriptBehaviourManager is destroyed. + /// Before Playmode exits or scripts are reloaded OnDestroy will be called on all created ScriptBehaviourManagers. + /// + protected virtual void OnDestroyManager() + { + } + + internal abstract void InternalUpdate(); + + /// + /// Execute the manager immediately. + /// + public void Update() + { +#if UNITY_EDITOR + sampler?.Begin(); +#endif + InternalUpdate(); + +#if UNITY_EDITOR + sampler?.End(); +#endif + } + } +} diff --git a/Unity.Entities/ScriptBehaviourManager.cs.meta b/Unity.Entities/ScriptBehaviourManager.cs.meta new file mode 100644 index 00000000..980977ef --- /dev/null +++ b/Unity.Entities/ScriptBehaviourManager.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 078984a67569143afaf36dff8137e304 +timeCreated: 1482868705 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/ScriptBehaviourUpdateOrder.cs b/Unity.Entities/ScriptBehaviourUpdateOrder.cs new file mode 100644 index 00000000..1d5e6622 --- /dev/null +++ b/Unity.Entities/ScriptBehaviourUpdateOrder.cs @@ -0,0 +1,888 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.Experimental.LowLevel; +using UnityEngine.Experimental.PlayerLoop; + +namespace Unity.Entities +{ + // Updating before or after an engine system guarantees it is in the same update phase as the dependency + // Update After a phase means in that pase but after all engine systems, Before a phase means in that phase but before all engine systems + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class UpdateBeforeAttribute : Attribute + { + public UpdateBeforeAttribute(Type systemType) + { + SystemType = systemType; + } + + public Type SystemType { get; } + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class UpdateAfterAttribute : Attribute + { + public UpdateAfterAttribute(Type systemType) + { + SystemType = systemType; + } + + public Type SystemType { get; } + } + + // Updating in a group means all dependencies from that group are inherited. A system can be in multiple goups + // There is nothing preventing systems from being in multiple groups, it can be added if there is a use-case for it + [AttributeUsage(AttributeTargets.Class)] + public class UpdateInGroupAttribute : Attribute + { + public UpdateInGroupAttribute(Type groupType) + { + GroupType = groupType; + } + + public Type GroupType { get; } + } + + public static class ScriptBehaviourUpdateOrder + { + // Try to find a system of the specified type in the default playerloop and update the min / max insertion position + private static void UpdateInsertionPos(DependantBehavior target, Type dep, PlayerLoopSystem defaultPlayerLoop, + bool after) + { + var pos = 0; + foreach (var sys in defaultPlayerLoop.subSystemList) + { + ++pos; + if (sys.type == dep) + { + if (after) + { + pos += sys.subSystemList.Length; + if (target.MinInsertPos < pos) + target.MinInsertPos = pos; + if (target.MaxInsertPos == 0 || target.MaxInsertPos > pos) + target.MaxInsertPos = pos; + } + else + { + if (target.MinInsertPos < pos) + target.MinInsertPos = pos; + if (target.MaxInsertPos == 0 || target.MaxInsertPos > pos) + target.MaxInsertPos = pos; + } + + return; + } + + var beginPos = pos; + var endPos = pos + sys.subSystemList.Length; + foreach (var subsys in sys.subSystemList) + { + if (subsys.type == dep) + { + if (after) + { + ++pos; + if (target.MinInsertPos < pos) + target.MinInsertPos = pos; + if (target.MaxInsertPos == 0 || target.MaxInsertPos > endPos) + target.MaxInsertPos = endPos; + } + else + { + if (target.MinInsertPos < beginPos) + target.MinInsertPos = beginPos; + if (target.MaxInsertPos == 0 || target.MaxInsertPos > pos) + target.MaxInsertPos = pos; + } + + return; + } + + ++pos; + } + } + + // System was not found + } + + private static void AddDependencies(DependantBehavior targetSystem, + IReadOnlyDictionary dependencies, + IReadOnlyDictionary allGroups, PlayerLoopSystem defaultPlayerLoop) + { + var target = targetSystem.Manager.GetType(); + var attribs = target.GetCustomAttributes(typeof(UpdateAfterAttribute), true); + foreach (var attr in attribs) + { + var attribDep = attr as UpdateAfterAttribute; + DependantBehavior otherSystem; + ScriptBehaviourGroup otherGroup; + if (dependencies.TryGetValue(attribDep.SystemType, out otherSystem)) + { + targetSystem.UpdateAfter.Add(attribDep.SystemType); + otherSystem.UpdateBefore.Add(target); + } + else if (allGroups.TryGetValue(attribDep.SystemType, out otherGroup)) + { + otherGroup.AddUpdateBeforeToAllChildBehaviours(targetSystem, dependencies); + } + else + { + UpdateInsertionPos(targetSystem, attribDep.SystemType, defaultPlayerLoop, true); + } + } + + attribs = target.GetCustomAttributes(typeof(UpdateBeforeAttribute), true); + foreach (var attr in attribs) + { + var attribDep = attr as UpdateBeforeAttribute; + DependantBehavior otherSystem; + ScriptBehaviourGroup otherGroup; + if (dependencies.TryGetValue(attribDep.SystemType, out otherSystem)) + { + targetSystem.UpdateBefore.Add(attribDep.SystemType); + otherSystem.UpdateAfter.Add(target); + } + else if (allGroups.TryGetValue(attribDep.SystemType, out otherGroup)) + { + otherGroup.AddUpdateAfterToAllChildBehaviours(targetSystem, dependencies); + } + else + { + UpdateInsertionPos(targetSystem, attribDep.SystemType, defaultPlayerLoop, false); + } + } + + attribs = target.GetCustomAttributes(typeof(UpdateInGroupAttribute), true); + foreach (var attr in attribs) + { + var attribDep = attr as UpdateInGroupAttribute; + ScriptBehaviourGroup group; + if (!allGroups.TryGetValue(attribDep.GroupType, out group)) + continue; + + DependantBehavior otherSystem; + ScriptBehaviourGroup otherGroup; + foreach (var dep in group.UpdateAfter) + if (dependencies.TryGetValue(dep, out otherSystem)) + { + targetSystem.UpdateAfter.Add(dep); + otherSystem.UpdateBefore.Add(target); + } + else if (allGroups.TryGetValue(dep, out otherGroup)) + { + otherGroup.AddUpdateBeforeToAllChildBehaviours(targetSystem, dependencies); + } + else + { + UpdateInsertionPos(targetSystem, dep, defaultPlayerLoop, true); + } + + foreach (var dep in group.UpdateBefore) + if (dependencies.TryGetValue(dep, out otherSystem)) + { + targetSystem.UpdateBefore.Add(dep); + otherSystem.UpdateAfter.Add(target); + } + else if (allGroups.TryGetValue(dep, out otherGroup)) + { + otherGroup.AddUpdateAfterToAllChildBehaviours(targetSystem, dependencies); + } + else + { + UpdateInsertionPos(targetSystem, dep, defaultPlayerLoop, false); + } + } + } + + public static void CollectGroups(IEnumerable activeManagers, + out Dictionary allGroups, out Dictionary dependencies) + { + allGroups = new Dictionary(); + dependencies = new Dictionary(); + foreach (var manager in activeManagers) + { + var attribs = manager.GetType().GetCustomAttributes(typeof(UpdateInGroupAttribute), true); + foreach (var attr in attribs) + { + var grp = attr as UpdateInGroupAttribute; + ScriptBehaviourGroup groupData; + if (!allGroups.TryGetValue(grp.GroupType, out groupData)) + groupData = new ScriptBehaviourGroup(grp.GroupType, allGroups); + groupData.Managers.Add(manager.GetType()); + } + + var dep = new DependantBehavior(manager); + dependencies.Add(manager.GetType(), dep); + } + } + + private static Dictionary BuildSystemGraph( + IEnumerable activeManagers, PlayerLoopSystem defaultPlayerLoop) + { + // Collect all groups and create empty dependency data + Dictionary allGroups; + Dictionary dependencies; + CollectGroups(activeManagers, out allGroups, out dependencies); + + // @TODO: apply additional sideloaded constraints here + + // Apply the update before / after dependencies + foreach (var manager in dependencies) + // @TODO: need to deal with extracting dependencies for GenericProcessComponentSystem + AddDependencies(manager.Value, dependencies, allGroups, defaultPlayerLoop); + + ValidateAndFixSystemGraph(dependencies); + + return dependencies; + } + + private static void ValidateAndFixSystemGraph(Dictionary dependencyGraph) + { + // Check for simple over constraints on engine systems + foreach (var typeAndSystem in dependencyGraph) + { + var system = typeAndSystem.Value; + if (system.MinInsertPos > system.MaxInsertPos) + { + Debug.LogError( + $"{system.Manager.GetType()} is over constrained with engine containts - ignoring dependencies"); + system.MinInsertPos = system.MaxInsertPos = 0; + } + + system.UnvalidatedSystemsUpdatingBefore = system.UpdateAfter.Count; + system.LongestSystemsUpdatingBeforeChain = 0; + system.LongestSystemsUpdatingAfterChain = 0; + } + + // Check for circular dependencies, start with all systems updating first, mark all systems it updates after as having one more validated dep and start over + var progress = true; + while (progress) + { + progress = false; + foreach (var typeAndSystem in dependencyGraph) + { + var system = typeAndSystem.Value; + if (system.UnvalidatedSystemsUpdatingBefore != 0) + continue; + + system.UnvalidatedSystemsUpdatingBefore = -1; + foreach (var nextInChain in system.UpdateBefore) + { + --dependencyGraph[nextInChain].UnvalidatedSystemsUpdatingBefore; + progress = true; + } + } + } + + // If some systems were found to have circular dependencies, drop all of them. This is a bit over aggressive - but it only happens on badly setup dependency chains + foreach (var typeAndSystem in dependencyGraph) + { + var system = typeAndSystem.Value; + if (system.UnvalidatedSystemsUpdatingBefore <= 0) + continue; + + Debug.LogError( + $"{system.Manager.GetType()} is in a chain of circular dependencies - ignoring dependencies"); + foreach (var after in system.UpdateAfter) + dependencyGraph[after].UpdateBefore.Remove(system.Manager.GetType()); + system.UpdateAfter.Clear(); + } + + // Validate that the chains are not over constrained with combinations of system and engine dependencies + foreach (var typeAndSystem in dependencyGraph) + { + var system = typeAndSystem.Value; + if (system.UpdateBefore.Count == 0) + ValidateAndFixSingleChainMaxPos(system, dependencyGraph, system.MaxInsertPos); + if (system.UpdateAfter.Count == 0) + ValidateAndFixSingleChainMinPos(system, dependencyGraph, system.MinInsertPos); + } + } + + private static void ValidateAndFixSingleChainMinPos(DependantBehavior system, + IReadOnlyDictionary dependencyGraph, int minInsertPos) + { + foreach (var nextInChain in system.UpdateBefore) + { + var nextSys = dependencyGraph[nextInChain]; + if (system.LongestSystemsUpdatingBeforeChain >= nextSys.LongestSystemsUpdatingBeforeChain) + nextSys.LongestSystemsUpdatingBeforeChain = system.LongestSystemsUpdatingBeforeChain + 1; + if (nextSys.MinInsertPos < minInsertPos) + nextSys.MinInsertPos = minInsertPos; + if (nextSys.MaxInsertPos > 0 && nextSys.MaxInsertPos < nextSys.MinInsertPos) + { + Debug.LogError( + $"{nextInChain} is over constrained with engine and system containts - ignoring dependencies"); + nextSys.MaxInsertPos = nextSys.MinInsertPos; + } + + ValidateAndFixSingleChainMinPos(nextSys, dependencyGraph, nextSys.MinInsertPos); + } + } + + private static void ValidateAndFixSingleChainMaxPos(DependantBehavior system, + Dictionary dependencyGraph, int maxInsertPos) + { + foreach (var prevInChain in system.UpdateAfter) + { + var prevSys = dependencyGraph[prevInChain]; + if (system.LongestSystemsUpdatingAfterChain >= prevSys.LongestSystemsUpdatingAfterChain) + prevSys.LongestSystemsUpdatingAfterChain = system.LongestSystemsUpdatingAfterChain + 1; + if (prevSys.MaxInsertPos == 0 || prevSys.MaxInsertPos > maxInsertPos) + prevSys.MaxInsertPos = maxInsertPos; + if (prevSys.MaxInsertPos > 0 && prevSys.MaxInsertPos < prevSys.MinInsertPos) + { + Debug.LogError( + $"{prevInChain} is over constrained with engine and system containts - ignoring dependencies"); + prevSys.MinInsertPos = prevSys.MaxInsertPos; + } + + ValidateAndFixSingleChainMaxPos(prevSys, dependencyGraph, prevSys.MaxInsertPos); + } + } + + private static void MarkSchedulingAndWaitingJobs(Dictionary dependencyGraph) + { + // @TODO: sync rules for read-only + var schedulers = new HashSet(); + foreach (var systemKeyValue in dependencyGraph) + { + var system = systemKeyValue.Value; + // @TODO: GenericProcessComponentSystem + // @TODO: attribute + if (!(system.Manager is JobComponentSystem)) + continue; + system.spawnsJobs = true; + schedulers.Add(system); + } + + foreach (var systemKeyValue in dependencyGraph) + { + var system = systemKeyValue.Value; + // @TODO: attribute for sync + if ((system.Manager as ComponentSystem)?.ComponentGroups == null) + continue; + + var waitComponent = new HashSet(); + foreach (var componentGroup in ((ComponentSystem) system.Manager).ComponentGroups) + foreach (var type in componentGroup.Types) + if (type.RequiresJobDependency) + waitComponent.Add(type.TypeIndex); + foreach (var scheduler in schedulers) + { + if (!(scheduler.Manager is ComponentSystem)) + continue; + // Check if the component groups overlaps + var scheduleComponent = new HashSet(); + foreach (var componentGroup in ((ComponentSystem) scheduler.Manager).ComponentGroups) + foreach (var type in componentGroup.Types) + if (type.RequiresJobDependency) + scheduleComponent.Add(type.TypeIndex); + var overlap = false; + foreach (var waitComp in waitComponent) + { + if (!scheduleComponent.Contains(waitComp)) + continue; + + overlap = true; + break; + } + + if (!overlap) + continue; + + system.WaitsForJobs = true; + break; + } + } + } + + public static PlayerLoopSystem InsertWorldManagersInPlayerLoop(PlayerLoopSystem defaultPlayerLoop, + params World[] worlds) + { + var systemList = new List(); + foreach (var world in worlds) + { + if (world.BehaviourManagers.Count() == 0) + continue; + systemList.AddRange(CreateSystemDependencyList(world.BehaviourManagers, defaultPlayerLoop)); + } + + var patchSystemList = new List(); + foreach (var world in worlds) + { + if (world.Patches.Count == 0) + continue; + patchSystemList.AddRange(world.Patches); + } + + var ecsPlayerLoop = CreatePlayerLoop(systemList, defaultPlayerLoop); + for (var i = 0; i < ecsPlayerLoop.subSystemList.Length; ++i) + { + var subSystemList = new List(); + + for (var j = 0; j < ecsPlayerLoop.subSystemList[i].subSystemList.Length; j++) + { + var next = ecsPlayerLoop.subSystemList[i].subSystemList[j]; + if (next.type.IsSubclassOf(typeof(ComponentSystem))) + for (var k = 0; k < patchSystemList.Count; k++) + { + var patch = new PlayerLoopSystem(); + patch.type = patchSystemList[k].GetType(); + var tmp = new DummyDelagateWrapper(patchSystemList[k]); + patch.updateDelegate = tmp.TriggerUpdate; + subSystemList.Add(patch); + } + + subSystemList.Add(next); + } + + ecsPlayerLoop.subSystemList[i].subSystemList = subSystemList.ToArray(); + } + + return ecsPlayerLoop; + } + + public static PlayerLoopSystem InsertManagersInPlayerLoop(IEnumerable activeManagers, + PlayerLoopSystem defaultPlayerLoop) + { + if (activeManagers.Count() == 0) + return defaultPlayerLoop; + + var list = CreateSystemDependencyList(activeManagers, defaultPlayerLoop); + return CreatePlayerLoop(list, defaultPlayerLoop); + } + + private static List CreateSystemDependencyList( + IEnumerable activeManagers, PlayerLoopSystem defaultPlayerLoop) + { + var dependencyGraph = BuildSystemGraph(activeManagers, defaultPlayerLoop); + + MarkSchedulingAndWaitingJobs(dependencyGraph); + + // Figure out which systems should be inserted early or late + var earlyUpdates = new HashSet(); + var normalUpdates = new HashSet(); + var lateUpdates = new HashSet(); + foreach (var dependency in dependencyGraph) + { + var system = dependency.Value; + if (system.spawnsJobs) + earlyUpdates.Add(system); + else if (system.WaitsForJobs) + lateUpdates.Add(system); + else + normalUpdates.Add(system); + } + + var depsToAdd = new List(); + while (true) + { + foreach (var sys in earlyUpdates) + foreach (var depType in sys.UpdateAfter) + { + var depSys = dependencyGraph[depType]; + if (normalUpdates.Remove(depSys) || lateUpdates.Remove(depSys)) + depsToAdd.Add(depSys); + } + + if (depsToAdd.Count == 0) + break; + foreach (var dep in depsToAdd) + earlyUpdates.Add(dep); + depsToAdd.Clear(); + } + + while (true) + { + foreach (var sys in lateUpdates) + foreach (var depType in sys.UpdateBefore) + { + var depSys = dependencyGraph[depType]; + if (normalUpdates.Remove(depSys)) + depsToAdd.Add(depSys); + } + + if (depsToAdd.Count == 0) + break; + foreach (var dep in depsToAdd) + lateUpdates.Add(dep); + depsToAdd.Clear(); + } + + var defaultPos = 0; + foreach (var sys in defaultPlayerLoop.subSystemList) + { + defaultPos += 1 + sys.subSystemList.Length; + if (sys.type == typeof(Update)) + break; + } + + var insertionBucketDict = new Dictionary(); + // increase the number of dependencies allowed by 1, starting from 0 and add all systems with that many at the first or last possible pos + // bucket idx is insertion point << 2 | 0,1,2 + // When adding propagate min or max through the chain + var processedChainLength = 0; + while (earlyUpdates.Count > 0 || lateUpdates.Count > 0) + { + foreach (var sys in earlyUpdates) + { + if (sys.LongestSystemsUpdatingBeforeChain != processedChainLength) + continue; + + if (sys.MinInsertPos == 0) + sys.MinInsertPos = defaultPos; + sys.MaxInsertPos = sys.MinInsertPos; + depsToAdd.Add(sys); + foreach (var nextSys in sys.UpdateBefore) + if (dependencyGraph[nextSys].MinInsertPos < sys.MinInsertPos) + dependencyGraph[nextSys].MinInsertPos = sys.MinInsertPos; + } + + foreach (var sys in lateUpdates) + { + if (sys.LongestSystemsUpdatingAfterChain != processedChainLength) + continue; + + if (sys.MaxInsertPos == 0) + sys.MaxInsertPos = defaultPos; + sys.MinInsertPos = sys.MaxInsertPos; + depsToAdd.Add(sys); + foreach (var prevSys in sys.UpdateAfter) + if (dependencyGraph[prevSys].MaxInsertPos == 0 || + dependencyGraph[prevSys].MaxInsertPos > sys.MaxInsertPos) + dependencyGraph[prevSys].MaxInsertPos = sys.MaxInsertPos; + } + + foreach (var sys in depsToAdd) + { + earlyUpdates.Remove(sys); + var isLate = lateUpdates.Remove(sys); + var subIndex = isLate ? 2 : 0; + + // Bucket to insert in is minPos == maxPos + var bucketIndex = (sys.MinInsertPos << 2) | subIndex; + InsertionBucket bucket; + if (!insertionBucketDict.TryGetValue(bucketIndex, out bucket)) + { + bucket = new InsertionBucket + { + InsertPos = sys.MinInsertPos, + InsertSubPos = subIndex + }; + insertionBucketDict.Add(bucketIndex, bucket); + } + + bucket.Systems.Add(sys); + } + + depsToAdd.Clear(); + ++processedChainLength; + } + + processedChainLength = 0; + while (normalUpdates.Count > 0) + { + foreach (var sys in normalUpdates) + { + if (sys.LongestSystemsUpdatingBeforeChain != processedChainLength) + continue; + + if (sys.MinInsertPos == 0) + sys.MinInsertPos = defaultPos; + sys.MaxInsertPos = sys.MinInsertPos; + depsToAdd.Add(sys); + foreach (var nextSys in sys.UpdateBefore) + if (dependencyGraph[nextSys].MinInsertPos < sys.MinInsertPos) + dependencyGraph[nextSys].MinInsertPos = sys.MinInsertPos; + } + + foreach (var sys in depsToAdd) + { + const int subIndex = 1; + normalUpdates.Remove(sys); + + // Bucket to insert in is minPos == maxPos + var bucketIndex = (sys.MinInsertPos << 2) | subIndex; + InsertionBucket bucket; + if (!insertionBucketDict.TryGetValue(bucketIndex, out bucket)) + { + bucket = new InsertionBucket(); + bucket.InsertPos = sys.MinInsertPos; + bucket.InsertSubPos = subIndex; + insertionBucketDict.Add(bucketIndex, bucket); + } + + bucket.Systems.Add(sys); + } + + depsToAdd.Clear(); + ++processedChainLength; + } + + return new List(insertionBucketDict.Values); + } + + private static PlayerLoopSystem CreatePlayerLoop(List insertionBuckets, + PlayerLoopSystem defaultPlayerLoop) + { + insertionBuckets.Sort(); + + // Insert the buckets at the appropriate place + var currentPos = 0; + var ecsPlayerLoop = new PlayerLoopSystem + { + subSystemList = new PlayerLoopSystem[defaultPlayerLoop.subSystemList.Length] + }; + var currentBucket = 0; + for (var i = 0; i < defaultPlayerLoop.subSystemList.Length; ++i) + { + var firstPos = currentPos + 1; + var lastPos = firstPos + defaultPlayerLoop.subSystemList[i].subSystemList.Length; + // Find all new things to insert here + var systemsToInsert = 0; + foreach (var bucket in insertionBuckets) + if (bucket.InsertPos >= firstPos && bucket.InsertPos <= lastPos) + systemsToInsert += bucket.Systems.Count; + ecsPlayerLoop.subSystemList[i] = defaultPlayerLoop.subSystemList[i]; + if (systemsToInsert > 0) + { + ecsPlayerLoop.subSystemList[i].subSystemList = + new PlayerLoopSystem[defaultPlayerLoop.subSystemList[i].subSystemList.Length + systemsToInsert]; + var dstPos = 0; + for (var srcPos = 0; + srcPos < defaultPlayerLoop.subSystemList[i].subSystemList.Length; + ++srcPos, ++dstPos) + { + while (currentBucket < insertionBuckets.Count && + insertionBuckets[currentBucket].InsertPos <= firstPos + srcPos) + { + foreach (var insert in insertionBuckets[currentBucket].Systems) + { + ecsPlayerLoop.subSystemList[i].subSystemList[dstPos].type = insert.Manager.GetType(); + var tmp = new DummyDelagateWrapper(insert.Manager); + ecsPlayerLoop.subSystemList[i].subSystemList[dstPos].updateDelegate = tmp.TriggerUpdate; + ++dstPos; + } + + ++currentBucket; + } + + ecsPlayerLoop.subSystemList[i].subSystemList[dstPos] = + defaultPlayerLoop.subSystemList[i].subSystemList[srcPos]; + } + + while (currentBucket < insertionBuckets.Count && + insertionBuckets[currentBucket].InsertPos <= lastPos) + { + foreach (var insert in insertionBuckets[currentBucket].Systems) + { + ecsPlayerLoop.subSystemList[i].subSystemList[dstPos].type = insert.Manager.GetType(); + var tmp = new DummyDelagateWrapper(insert.Manager); + ecsPlayerLoop.subSystemList[i].subSystemList[dstPos].updateDelegate = tmp.TriggerUpdate; + ++dstPos; + } + + ++currentBucket; + } + } + + currentPos = lastPos; + } + + return ecsPlayerLoop; + } + + public static void UpdatePlayerLoop(params World[] worlds) + { + var defaultLoop = PlayerLoop.GetDefaultPlayerLoop(); + + if (worlds.Length > 0) + { + var ecsLoop = InsertWorldManagersInPlayerLoop(defaultLoop, worlds); + SetPlayerLoopAndNotify(ecsLoop); + } + else + { + SetPlayerLoopAndNotify(defaultLoop); + } + } + + public static event Action OnSetPlayerLoop; + + public static void SetPlayerLoopAndNotify(PlayerLoopSystem playerLoop) + { + PlayerLoop.SetPlayerLoop(playerLoop); + OnSetPlayerLoop?.Invoke(playerLoop); + } + + // FIXME: HACK! - mono 4.6 has problems invoking virtual methods as delegates from native, so wrap the invocation in a non-virtual class + private class DummyDelagateWrapper + { + private readonly ScriptBehaviourManager m_Manager; + + public DummyDelagateWrapper(ScriptBehaviourManager man) + { + m_Manager = man; + } + + public void TriggerUpdate() + { + m_Manager.Update(); + } + } + + public class ScriptBehaviourGroup + { + private readonly List m_Groups = new List(); + public readonly List Managers = new List(); + public readonly HashSet UpdateAfter = new HashSet(); + public readonly HashSet UpdateBefore = new HashSet(); + + private readonly Type m_GroupType; + private readonly List m_Parents = new List(); + + public ScriptBehaviourGroup(Type grpType, IDictionary allGroups, + HashSet circularCheck = null) + { + m_GroupType = grpType; + + var attribs = grpType.GetCustomAttributes(typeof(UpdateAfterAttribute), true); + foreach (var attr in attribs) + { + var attribDep = attr as UpdateAfterAttribute; + UpdateAfter.Add(attribDep.SystemType); + } + + attribs = grpType.GetCustomAttributes(typeof(UpdateBeforeAttribute), true); + foreach (var attr in attribs) + { + var attribDep = attr as UpdateBeforeAttribute; + UpdateBefore.Add(attribDep.SystemType); + } + + allGroups.Add(m_GroupType, this); + + attribs = m_GroupType.GetCustomAttributes(typeof(UpdateInGroupAttribute), true); + foreach (var attr in attribs) + { + if (circularCheck == null) circularCheck = new HashSet {m_GroupType}; + var parentGrp = attr as UpdateInGroupAttribute; + if (!circularCheck.Add(parentGrp.GroupType)) + { + // Found circular dependency + var msg = "Found circular chain in update groups involving: "; + var firstType = true; + foreach (var circularType in circularCheck) + { + msg += (firstType ? "" : ", ") + circularType; + firstType = false; + } + + Debug.LogError(msg); + } + + ScriptBehaviourGroup parentGroupData; + if (!allGroups.TryGetValue(parentGrp.GroupType, out parentGroupData)) + parentGroupData = new ScriptBehaviourGroup(parentGrp.GroupType, allGroups, circularCheck); + circularCheck.Remove(parentGrp.GroupType); + parentGroupData.m_Groups.Add(this); + m_Parents.Add(parentGroupData); + + foreach (var dep in parentGroupData.UpdateBefore) + UpdateBefore.Add(dep); + foreach (var dep in parentGroupData.UpdateAfter) + UpdateAfter.Add(dep); + } + } + + public void AddUpdateBeforeToAllChildBehaviours(DependantBehavior target, + IReadOnlyDictionary dependencies) + { + var dep = target.Manager.GetType(); + foreach (var manager in Managers) + { + DependantBehavior managerDep; + if (!dependencies.TryGetValue(manager, out managerDep)) + continue; + + target.UpdateAfter.Add(manager); + managerDep.UpdateBefore.Add(dep); + } + + foreach (var group in m_Groups) + group.AddUpdateBeforeToAllChildBehaviours(target, dependencies); + } + + public void AddUpdateAfterToAllChildBehaviours(DependantBehavior target, + IReadOnlyDictionary dependencies) + { + var dep = target.Manager.GetType(); + foreach (var manager in Managers) + { + DependantBehavior managerDep; + if (!dependencies.TryGetValue(manager, out managerDep)) + continue; + + target.UpdateBefore.Add(manager); + managerDep.UpdateAfter.Add(dep); + } + + foreach (var group in m_Groups) + group.AddUpdateAfterToAllChildBehaviours(target, dependencies); + } + } + + public class DependantBehavior + { + public readonly ScriptBehaviourManager Manager; + public readonly HashSet UpdateAfter = new HashSet(); + public readonly HashSet UpdateBefore = new HashSet(); + public int LongestSystemsUpdatingAfterChain; + public int LongestSystemsUpdatingBeforeChain; + public int MaxInsertPos; + + public int MinInsertPos; + public bool spawnsJobs; + + public int UnvalidatedSystemsUpdatingBefore; + public bool WaitsForJobs; + + public DependantBehavior(ScriptBehaviourManager man) + { + Manager = man; + MinInsertPos = 0; + MaxInsertPos = 0; + spawnsJobs = false; + WaitsForJobs = false; + + UnvalidatedSystemsUpdatingBefore = 0; + LongestSystemsUpdatingBeforeChain = 0; + LongestSystemsUpdatingAfterChain = 0; + } + } + + private class InsertionBucket : IComparable + { + public readonly List Systems; + public int InsertPos; + public int InsertSubPos; + + public InsertionBucket() + { + InsertPos = 0; + InsertSubPos = 0; + Systems = new List(); + } + + public int CompareTo(object other) + { + var otherBucket = other as InsertionBucket; + if (InsertPos == otherBucket.InsertPos) + return InsertSubPos - otherBucket.InsertSubPos; + return InsertPos - otherBucket.InsertPos; + } + } + } +} diff --git a/Unity.Entities/ScriptBehaviourUpdateOrder.cs.meta b/Unity.Entities/ScriptBehaviourUpdateOrder.cs.meta new file mode 100644 index 00000000..a50ae08d --- /dev/null +++ b/Unity.Entities/ScriptBehaviourUpdateOrder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d68ef70bb1ef314a889a68466cf7dae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/SharedComponentManager.cs b/Unity.Entities/SharedComponentManager.cs new file mode 100644 index 00000000..e10bd657 --- /dev/null +++ b/Unity.Entities/SharedComponentManager.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.Assertions; + +namespace Unity.Entities +{ + internal class SharedComponentDataManager + { + private NativeMultiHashMap m_HashLookup = new NativeMultiHashMap(128, Allocator.Persistent); + + private List m_SharedComponentData = new List(); + private NativeList m_SharedComponentRefCount = new NativeList(0, Allocator.Persistent); + private NativeList m_SharedComponentType = new NativeList(0, Allocator.Persistent); + private NativeList m_SharedComponentVersion = new NativeList(0, Allocator.Persistent); + + public SharedComponentDataManager() + { + m_SharedComponentData.Add(null); + m_SharedComponentRefCount.Add(1); + m_SharedComponentVersion.Add(1); + m_SharedComponentType.Add(-1); + } + + public void Dispose() + { + m_SharedComponentType.Dispose(); + m_SharedComponentRefCount.Dispose(); + m_SharedComponentVersion.Dispose(); + m_SharedComponentData.Clear(); + m_SharedComponentData = null; + m_HashLookup.Dispose(); + } + + public void GetAllUniqueSharedComponents(List sharedComponentValues) + where T : struct, ISharedComponentData + { + sharedComponentValues.Add(default(T)); + for (var i = 1; i != m_SharedComponentData.Count; i++) + { + var data = m_SharedComponentData[i]; + if (data != null && data.GetType() == typeof(T)) + sharedComponentValues.Add((T) m_SharedComponentData[i]); + } + } + + public int InsertSharedComponent(T newData) where T : struct + { + var typeIndex = TypeManager.GetTypeIndex(); + var index = FindSharedComponentIndex(TypeManager.GetTypeIndex(), newData); + + if (index == 0) return 0; + + if (index != -1) + { + m_SharedComponentRefCount[index]++; + return index; + } + + var fastLayout = TypeManager.GetComponentType(typeIndex).FastEqualityLayout; + var hashcode = FastEquality.GetHashCode(ref newData, fastLayout); + return Add(typeIndex, hashcode, newData); + } + + private unsafe int FindSharedComponentIndex(int typeIndex, T newData) where T : struct + { + var defaultVal = default(T); + var fastLayout = TypeManager.GetComponentType(typeIndex).FastEqualityLayout; + if (FastEquality.Equals(ref defaultVal, ref newData, fastLayout)) + return 0; + return FindNonDefaultSharedComponentIndex(typeIndex, FastEquality.GetHashCode(ref newData, fastLayout), + UnsafeUtility.AddressOf(ref newData), fastLayout); + } + + private unsafe int FindNonDefaultSharedComponentIndex(int typeIndex, int hashCode, void* newData, + FastEquality.Layout[] layout) + { + int itemIndex; + NativeMultiHashMapIterator iter; + + if (!m_HashLookup.TryGetFirstValue(hashCode, out itemIndex, out iter)) + return -1; + + do + { + var data = m_SharedComponentData[itemIndex]; + if (data != null && m_SharedComponentType[itemIndex] == typeIndex) + { + ulong handle; + var value = PinGCObjectAndGetAddress(data, out handle); + var res = FastEquality.Equals(newData, value, layout); + UnsafeUtility.ReleaseGCObject(handle); + + if (res) + return itemIndex; + } + } while (m_HashLookup.TryGetNextValue(out itemIndex, ref iter)); + + return -1; + } + + internal unsafe int InsertSharedComponentAssumeNonDefault(int typeIndex, int hashCode, object newData, + FastEquality.Layout[] layout) + { + ulong handle; + var newDataPtr = PinGCObjectAndGetAddress(newData, out handle); + + var index = FindNonDefaultSharedComponentIndex(typeIndex, hashCode, newDataPtr, layout); + + UnsafeUtility.ReleaseGCObject(handle); + + if (-1 == index) + index = Add(typeIndex, hashCode, newData); + else + m_SharedComponentRefCount[index] += 1; + + return index; + } + + private int Add(int typeIndex, int hashCode, object newData) + { + var index = m_SharedComponentData.Count; + m_HashLookup.Add(hashCode, index); + m_SharedComponentData.Add(newData); + m_SharedComponentRefCount.Add(1); + m_SharedComponentVersion.Add(1); + m_SharedComponentType.Add(typeIndex); + return index; + } + + + public void IncrementSharedComponentVersion(int index) + { + m_SharedComponentVersion[index]++; + } + + public int GetSharedComponentVersion(T sharedData) where T : struct + { + var index = FindSharedComponentIndex(TypeManager.GetTypeIndex(), sharedData); + return index == -1 ? 0 : m_SharedComponentVersion[index]; + } + + public T GetSharedComponentData(int index) where T : struct + { + if (index == 0) + return default(T); + + return (T) m_SharedComponentData[index]; + } + + public object GetSharedComponentDataBoxed(int index) + { + if (index == 0) + return Activator.CreateInstance(TypeManager.GetType(m_SharedComponentType[index])); + + return m_SharedComponentData[index]; + } + + public void AddReference(int index) + { + if (index != 0) + ++m_SharedComponentRefCount[index]; + } + + private static unsafe int GetHashCodeFast(object target, FastEquality.Layout[] fastLayout) + { + ulong handle; + var ptr = PinGCObjectAndGetAddress(target, out handle); + var hashCode = FastEquality.GetHashCode(ptr, fastLayout); + UnsafeUtility.ReleaseGCObject(handle); + + return hashCode; + } + + private static unsafe void* PinGCObjectAndGetAddress(object target, out ulong handle) + { + var ptr = UnsafeUtility.PinGCObjectAndGetAddress(target, out handle); + return (byte*) ptr + TypeManager.ObjectOffset; + } + + + public void RemoveReference(int index) + { + if (index == 0) + return; + + var newCount = --m_SharedComponentRefCount[index]; + Assert.IsTrue(newCount >= 0); + + if (newCount != 0) + return; + + var typeIndex = m_SharedComponentType[index]; + + var fastLayout = TypeManager.GetComponentType(typeIndex).FastEqualityLayout; + var hashCode = GetHashCodeFast(m_SharedComponentData[index], fastLayout); + + m_SharedComponentData[index] = null; + m_SharedComponentType[index] = -1; + + int itemIndex; + NativeMultiHashMapIterator iter; + if (!m_HashLookup.TryGetFirstValue(hashCode, out itemIndex, out iter)) + return; + + do + { + if (itemIndex != index) + continue; + + m_HashLookup.Remove(iter); + break; + } while (m_HashLookup.TryGetNextValue(out itemIndex, ref iter)); + } + + + public unsafe void MoveSharedComponents(SharedComponentDataManager srcSharedComponents, + int* sharedComponentIndices, int sharedComponentIndicesCount) + { + for (var i = 0; i != sharedComponentIndicesCount; i++) + { + var srcIndex = sharedComponentIndices[i]; + if (srcIndex == 0) + continue; + + var srcData = srcSharedComponents.m_SharedComponentData[srcIndex]; + var typeIndex = srcSharedComponents.m_SharedComponentType[srcIndex]; + + var fastLayout = TypeManager.GetComponentType(typeIndex).FastEqualityLayout; + var hashCode = GetHashCodeFast(srcData, fastLayout); + + var dstIndex = InsertSharedComponentAssumeNonDefault(typeIndex, hashCode, srcData, fastLayout); + srcSharedComponents.RemoveReference(srcIndex); + + sharedComponentIndices[i] = dstIndex; + } + } + } +} diff --git a/Unity.Entities/SharedComponentManager.cs.meta b/Unity.Entities/SharedComponentManager.cs.meta new file mode 100644 index 00000000..65b7e49c --- /dev/null +++ b/Unity.Entities/SharedComponentManager.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 95cfe25ce1b5145f4b332cdd81c6e758 +timeCreated: 1506175435 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/SortingUtilities.cs b/Unity.Entities/SortingUtilities.cs new file mode 100644 index 00000000..a05e8d99 --- /dev/null +++ b/Unity.Entities/SortingUtilities.cs @@ -0,0 +1,596 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Unity.Entities +{ + internal struct SortingUtilities + { + public static unsafe void InsertSorted(ComponentType* data, int length, ComponentType newValue) + { + while (length > 0 && newValue < data[length - 1]) + { + data[length] = data[length - 1]; + --length; + } + + data[length] = newValue; + } + + public static unsafe void InsertSorted(ComponentTypeInArchetype* data, int length, ComponentType newValue) + { + var newVal = new ComponentTypeInArchetype(newValue); + while (length > 0 && newVal < data[length - 1]) + { + data[length] = data[length - 1]; + --length; + } + + data[length] = newVal; + } + } + + /// + /// Merge sort index list referencing NativeArray values. + /// Provide list of shared values, indices to shared values, and lists of source i + /// value indices with identical shared value. + /// As an example: + /// Given Source NativeArray: AAABBCCAB + /// Provides: + /// Shared value indices: 000112201 + /// Shared value counts: 432 + /// Shared values: ABC + /// Sorted indices: 012734856 + /// Shared value start offsets (into sorted indices): 047 + /// + /// + public struct NativeArraySharedValues : IDisposable + where T : struct, IComparable + { + private NativeArray m_Buffer; + [ReadOnly] private readonly NativeArray m_Source; + private int m_SortedBuffer; + + public NativeArraySharedValues(NativeArray source, Allocator allocator) + { + m_Buffer = new NativeArray(source.Length * 4 + 1, allocator); + m_Source = source; + m_SortedBuffer = 0; + } + + [ComputeJobOptimization] + private struct InitializeIndices : IJobParallelFor + { + public NativeArray buffer; + + public void Execute(int index) + { + buffer[index] = index; + } + } + + [ComputeJobOptimization] + private struct MergeSortedPairs : IJobParallelFor + { + [NativeDisableParallelForRestriction] public NativeArray buffer; + [ReadOnly] public NativeArray source; + public int sortedCount; + public int outputBuffer; + + public void Execute(int index) + { + var mergedCount = sortedCount * 2; + var offset = index * mergedCount; + var inputOffset = (outputBuffer ^ 1) * source.Length; + var outputOffset = outputBuffer * source.Length; + var leftCount = sortedCount; + var rightCount = sortedCount; + var leftNext = 0; + var rightNext = 0; + + for (var i = 0; i < mergedCount; i++) + if (leftNext < leftCount && rightNext < rightCount) + { + var leftIndex = buffer[inputOffset + offset + leftNext]; + var rightIndex = buffer[inputOffset + offset + leftCount + rightNext]; + var leftValue = source[leftIndex]; + var rightValue = source[rightIndex]; + + if (rightValue.CompareTo(leftValue) < 0) + { + buffer[outputOffset + offset + i] = rightIndex; + rightNext++; + } + else + { + buffer[outputOffset + offset + i] = leftIndex; + leftNext++; + } + } + else if (leftNext < leftCount) + { + var leftIndex = buffer[inputOffset + offset + leftNext]; + buffer[outputOffset + offset + i] = leftIndex; + leftNext++; + } + else + { + var rightIndex = buffer[inputOffset + offset + leftCount + rightNext]; + buffer[outputOffset + offset + i] = rightIndex; + rightNext++; + } + } + } + + [ComputeJobOptimization] + private struct MergeLeft : IJobParallelFor + { + [NativeDisableParallelForRestriction] public NativeArray buffer; + [ReadOnly] public NativeArray source; + public int leftCount; + public int rightCount; + public int startIndex; + public int outputBuffer; + + // On left, equal is equivalent to less-than + private int FindInsertNext(int startOffset, int minNext, int maxNext, T testValue) + { + if (minNext == maxNext) + { + var index = buffer[startOffset + minNext]; + var value = source[index]; + var compare = testValue.CompareTo(value); + if (compare <= 0) return minNext; + return minNext + 1; + } + + var midNext = minNext + (maxNext - minNext) / 2; + { + var index = buffer[startOffset + midNext]; + var value = source[index]; + var compare = testValue.CompareTo(value); + if (compare <= 0) + return FindInsertNext(startOffset, minNext, math.max(midNext - 1, minNext), testValue); + } + return FindInsertNext(startOffset, math.min(midNext + 1, maxNext), maxNext, testValue); + } + + public void Execute(int leftNext) + { + var inputOffset = (outputBuffer ^ 1) * source.Length; + var outputOffset = outputBuffer * source.Length; + var leftIndex = buffer[inputOffset + startIndex + leftNext]; + var leftValue = source[leftIndex]; + var rightNext = FindInsertNext(inputOffset + startIndex + leftCount, 0, rightCount - 1, leftValue); + var mergeNext = leftNext + rightNext; + + buffer[outputOffset + startIndex + mergeNext] = leftIndex; + } + } + + [ComputeJobOptimization] + private struct MergeRight : IJobParallelFor + { + [NativeDisableParallelForRestriction] public NativeArray buffer; + [ReadOnly] public NativeArray source; + public int leftCount; + public int rightCount; + public int startIndex; + public int outputBuffer; + + // On right, equal is equivalent to greater-than + private int FindInsertNext(int startOffset, int minNext, int maxNext, T testValue) + { + if (minNext == maxNext) + { + var index = buffer[startOffset + minNext]; + var value = source[index]; + var compare = testValue.CompareTo(value); + if (compare < 0) return minNext; + return minNext + 1; + } + + var midNext = minNext + (maxNext - minNext) / 2; + { + var index = buffer[startOffset + midNext]; + var value = source[index]; + var compare = testValue.CompareTo(value); + if (compare < 0) + return FindInsertNext(startOffset, minNext, math.max(midNext - 1, minNext), testValue); + } + return FindInsertNext(startOffset, math.min(midNext + 1, maxNext), maxNext, testValue); + } + + public void Execute(int rightNext) + { + var inputOffset = (outputBuffer ^ 1) * source.Length; + var outputOffset = outputBuffer * source.Length; + var rightIndex = buffer[inputOffset + startIndex + leftCount + rightNext]; + var rightValue = source[rightIndex]; + var leftNext = FindInsertNext(inputOffset + startIndex, 0, leftCount - 1, rightValue); + var mergeNext = rightNext + leftNext; + + buffer[outputOffset + startIndex + mergeNext] = rightIndex; + } + } + + [ComputeJobOptimization] + private struct CopyRemainder : IJobParallelFor + { + [NativeDisableParallelForRestriction] public NativeArray buffer; + [ReadOnly] public NativeArray source; + public int startIndex; + public int outputBuffer; + + public void Execute(int index) + { + var inputOffset = (outputBuffer ^ 1) * source.Length; + var outputOffset = outputBuffer * source.Length; + var outputIndex = outputOffset + startIndex + index; + var inputIndex = inputOffset + startIndex + index; + var valueIndex = buffer[inputIndex]; + buffer[outputIndex] = valueIndex; + } + } + + private JobHandle MergeSortedLists(JobHandle inputDeps, int sortedCount, int outputBuffer) + { + var pairCount = m_Source.Length / (sortedCount * 2); + + var mergeSortedPairsJobHandle = inputDeps; + + if (pairCount <= 4) + { + for (var i = 0; i < pairCount; i++) + { + var mergeRemainderLeftJob = new MergeLeft + { + startIndex = i * sortedCount * 2, + buffer = m_Buffer, + source = m_Source, + leftCount = sortedCount, + rightCount = sortedCount, + outputBuffer = outputBuffer + }; + // There's no overlap, but write to the same array, so extra dependency: + mergeSortedPairsJobHandle = + mergeRemainderLeftJob.Schedule(sortedCount, 64, mergeSortedPairsJobHandle); + + var mergeRemainderRightJob = new MergeRight + { + startIndex = i * sortedCount * 2, + buffer = m_Buffer, + source = m_Source, + leftCount = sortedCount, + rightCount = sortedCount, + outputBuffer = outputBuffer + }; + // There's no overlap, but write to the same array, so extra dependency: + mergeSortedPairsJobHandle = + mergeRemainderRightJob.Schedule(sortedCount, 64, mergeSortedPairsJobHandle); + } + } + else + { + var mergeSortedPairsJob = new MergeSortedPairs + { + buffer = m_Buffer, + source = m_Source, + sortedCount = sortedCount, + outputBuffer = outputBuffer + }; + mergeSortedPairsJobHandle = mergeSortedPairsJob.Schedule(pairCount, (pairCount + 1) / 8, inputDeps); + } + + var remainder = m_Source.Length - pairCount * sortedCount * 2; + if (remainder > sortedCount) + { + var mergeRemainderLeftJob = new MergeLeft + { + startIndex = pairCount * sortedCount * 2, + buffer = m_Buffer, + source = m_Source, + leftCount = sortedCount, + rightCount = remainder - sortedCount, + outputBuffer = outputBuffer + }; + // There's no overlap, but write to the same array, so extra dependency: + var mergeLeftJobHandle = mergeRemainderLeftJob.Schedule(sortedCount, 64, mergeSortedPairsJobHandle); + + var mergeRemainderRightJob = new MergeRight + { + startIndex = pairCount * sortedCount * 2, + buffer = m_Buffer, + source = m_Source, + leftCount = sortedCount, + rightCount = remainder - sortedCount, + outputBuffer = outputBuffer + }; + // There's no overlap, but write to the same array, so extra dependency: + var mergeRightJobHandle = + mergeRemainderRightJob.Schedule(remainder - sortedCount, 64, mergeLeftJobHandle); + return mergeRightJobHandle; + } + + if (remainder > 0) + { + var copyRemainderPairJob = new CopyRemainder + { + startIndex = pairCount * sortedCount * 2, + buffer = m_Buffer, + source = m_Source, + outputBuffer = outputBuffer + }; + + // There's no overlap, but write to the same array, so extra dependency: + var copyRemainderPairJobHandle = + copyRemainderPairJob.Schedule(remainder, (pairCount + 1) / 8, mergeSortedPairsJobHandle); + return copyRemainderPairJobHandle; + } + + return mergeSortedPairsJobHandle; + } + + [ComputeJobOptimization] + private struct AssignSharedValues : IJob + { + public NativeArray buffer; + [ReadOnly] public NativeArray source; + public int sortedBuffer; + + public void Execute() + { + var sortedIndicesOffset = sortedBuffer * source.Length; + var sharedValueIndicesOffset = (sortedBuffer ^ 1) * source.Length; + var sharedValueIndexCountOffset = 2 * source.Length; + var sharedValueStartIndicesOffset = 3 * source.Length; + var sharedValueCountOffset = 4 * source.Length; + + var index = 0; + var valueIndex = buffer[sortedIndicesOffset + index]; + var sharedValue = source[valueIndex]; + var sharedValueCount = 1; + buffer[sharedValueIndicesOffset + valueIndex] = 0; + buffer[sharedValueStartIndicesOffset + (sharedValueCount - 1)] = index; + buffer[sharedValueIndexCountOffset + (sharedValueCount - 1)] = 1; + index++; + + while (index < source.Length) + { + valueIndex = buffer[sortedIndicesOffset + index]; + var value = source[valueIndex]; + if (value.CompareTo(sharedValue) != 0) + { + sharedValueCount++; + sharedValue = value; + buffer[sharedValueStartIndicesOffset + (sharedValueCount - 1)] = index; + buffer[sharedValueIndexCountOffset + (sharedValueCount - 1)] = 1; + buffer[sharedValueIndicesOffset + valueIndex] = sharedValueCount - 1; + } + else + { + buffer[sharedValueIndexCountOffset + (sharedValueCount - 1)]++; + buffer[sharedValueIndicesOffset + valueIndex] = sharedValueCount - 1; + } + + index++; + } + + buffer[sharedValueCountOffset] = sharedValueCount; + } + } + + private JobHandle Sort(JobHandle inputDeps) + { + var sortedCount = 1; + var outputBuffer = 1; + do + { + inputDeps = MergeSortedLists(inputDeps, sortedCount, outputBuffer); + sortedCount *= 2; + outputBuffer ^= 1; + } while (sortedCount < m_Source.Length); + + m_SortedBuffer = outputBuffer ^ 1; + + return inputDeps; + } + + private JobHandle ResolveSharedGroups(JobHandle inputDeps) + { + var assignSharedValuesJob = new AssignSharedValues + { + buffer = m_Buffer, + source = m_Source, + sortedBuffer = m_SortedBuffer + }; + var assignSharedValuesJobHandle = assignSharedValuesJob.Schedule(inputDeps); + return assignSharedValuesJobHandle; + } + + /// + /// Schedule jobs to collect and sort shared values. + /// + /// Dependent JobHandle + /// JobHandle + public JobHandle Schedule(JobHandle inputDeps) + { + if (m_Source.Length <= 1) return inputDeps; + var initializeIndicesJob = new InitializeIndices + { + buffer = m_Buffer + }; + var initializeIndicesJobHandle = + initializeIndicesJob.Schedule(m_Source.Length, (m_Source.Length + 1) / 8, inputDeps); + var sortJobHandle = Sort(initializeIndicesJobHandle); + var resolveSharedGroupsJobHandle = ResolveSharedGroups(sortJobHandle); + return resolveSharedGroupsJobHandle; + } + + /// + /// Indices into source NativeArray sorted by value + /// + /// Index NativeArray where each element refers to alement ini source NativeArray + public unsafe NativeArray GetSortedIndices() + { + var rawIndices = (int*) m_Buffer.GetUnsafeReadOnlyPtr() + m_SortedBuffer * m_Source.Length; + var arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(rawIndices, m_Source.Length, + Allocator.Invalid); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + SortedIndicesSetSafetyHandle(ref arr); +#endif + return arr; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Uncomment when NativeArrayUnsafeUtility includes these conditional checks + // [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void SortedIndicesSetSafetyHandle(ref NativeArray arr) + { + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, + NativeArrayUnsafeUtility.GetAtomicSafetyHandle(m_Buffer)); + } +#endif + + /// + /// Number of shared (unique) values in source NativeArray + /// + public int SharedValueCount => m_Buffer[m_Source.Length * 4]; + + /// + /// Index of shared value + /// + /// Index of source value + /// + public int GetSharedIndexBySourceIndex(int index) + { + var sharedValueIndicesOffset = (m_SortedBuffer ^ 1) * m_Source.Length; + var sharedValueIndex = m_Buffer[sharedValueIndicesOffset + index]; + return sharedValueIndex; + } + + public unsafe NativeArray GetSharedIndexArray() + { + // Capacity cannot be changed, so offset is valid. + var rawIndices = (int*) NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer) + + (m_SortedBuffer ^ 1) * m_Source.Length; + var arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(rawIndices, m_Source.Length, + Allocator.Invalid); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + SharedIndexArraySetSafetyHandle(ref arr); +#endif + return arr; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Uncomment when NativeArrayUnsafeUtility includes these conditional checks + // [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void SharedIndexArraySetSafetyHandle(ref NativeArray arr) + { + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, + NativeArrayUnsafeUtility.GetAtomicSafetyHandle(m_Buffer)); + } +#endif + + /// + /// Array of indices into source NativeArray which share the same source value + /// + /// Index of source value + /// + public NativeArray GetSharedValueIndicesBySourceIndex(int index) + { + var sharedValueIndicesOffset = (m_SortedBuffer ^ 1) * m_Source.Length; + var sharedValueIndex = m_Buffer[sharedValueIndicesOffset + index]; + return GetSharedValueIndicesBySharedIndex(sharedValueIndex); + } + + /// + /// Number of values which share the same value. + /// + /// Number of values which share the same value. + /// + public int GetSharedValueIndexCountBySourceIndex(int index) + { + var sharedValueIndex = GetSharedIndexBySourceIndex(index); + var sharedValueIndexCountOffset = 2 * m_Source.Length; + var sharedValueIndexCount = m_Buffer[sharedValueIndexCountOffset + sharedValueIndex]; + return sharedValueIndexCount; + } + + public unsafe NativeArray GetSharedValueIndexCountArray() + { + // Capacity cannot be changed, so offset is valid. + var rawIndices = (int*) NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer) + + 2 * m_Source.Length; + var arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(rawIndices, m_Source.Length, + Allocator.Invalid); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + SharedValueIndexCountArraySetSafetyHandle(ref arr); +#endif + return arr; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Uncomment when NativeArrayUnsafeUtility includes these conditional checks + // [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void SharedValueIndexCountArraySetSafetyHandle(ref NativeArray arr) + { + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, + NativeArrayUnsafeUtility.GetAtomicSafetyHandle(m_Buffer)); + } +#endif + + /// + /// Array of indices into source NativeArray which share the same source value + /// + /// Index of shared value + /// + public unsafe NativeArray GetSharedValueIndicesBySharedIndex(int index) + { + var sharedValueIndexCountOffset = 2 * m_Source.Length; + var sharedValueIndexCount = m_Buffer[sharedValueIndexCountOffset + index]; + var sharedValueStartIndicesOffset = 3 * m_Source.Length; + var sharedValueStartIndex = m_Buffer[sharedValueStartIndicesOffset + index]; + var sortedValueOffset = m_SortedBuffer * m_Source.Length; + + // Capacity cannot be changed, so offset is valid. + var rawIndices = (int*) NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Buffer) + + (sortedValueOffset + sharedValueStartIndex); + var arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(rawIndices, sharedValueIndexCount, + Allocator.Invalid); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + SharedValueIndicesSetSafetyHandle(ref arr); +#endif + return arr; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Uncomment when NativeArrayUnsafeUtility includes these conditional checks + // [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void SharedValueIndicesSetSafetyHandle(ref NativeArray arr) + { + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, + NativeArrayUnsafeUtility.GetAtomicSafetyHandle(m_Buffer)); + } +#endif + + /// + /// Get internal buffer for disposal + /// + /// + public NativeArray GetBuffer() + { + return m_Buffer; + } + + /// + /// Dispose internal buffer + /// + public void Dispose() + { + m_Buffer.Dispose(); + } + } +} diff --git a/Unity.Entities/SortingUtilities.cs.meta b/Unity.Entities/SortingUtilities.cs.meta new file mode 100644 index 00000000..3aa0867c --- /dev/null +++ b/Unity.Entities/SortingUtilities.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 176a7bc19f11343e5a8c86ea55b8a7e6 +timeCreated: 1504708403 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Stubs.meta b/Unity.Entities/Stubs.meta new file mode 100644 index 00000000..4bd4fbd1 --- /dev/null +++ b/Unity.Entities/Stubs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 994e918d4e98f4be2876dbefadd6045f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Stubs/Unity.Assertions.meta b/Unity.Entities/Stubs/Unity.Assertions.meta new file mode 100644 index 00000000..b9a8542d --- /dev/null +++ b/Unity.Entities/Stubs/Unity.Assertions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a140e7ef2d2cf436ab7a53116944ee99 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Stubs/Unity.Assertions/Assert.cs b/Unity.Entities/Stubs/Unity.Assertions/Assert.cs new file mode 100644 index 00000000..512d029b --- /dev/null +++ b/Unity.Entities/Stubs/Unity.Assertions/Assert.cs @@ -0,0 +1,111 @@ +using System.Diagnostics; + +namespace Unity.Assertions +{ + // TODO: provide an implementation of Unity.Assertions.Assert that does not rely on UnityEngine. + [DebuggerStepThrough] + internal static class Assert + { + [Conditional("UNITY_ASSERTIONS")] + public static void IsTrue(bool condition) + { + if (condition) + return; + + UnityEngine.Assertions.Assert.IsTrue(condition); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void IsTrue(bool condition, string message) + { + if (condition) + return; + + UnityEngine.Assertions.Assert.IsTrue(condition, message); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void IsFalse(bool condition) + { + if (!condition) + return; + + UnityEngine.Assertions.Assert.IsFalse(condition); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void IsFalse(bool condition, string message) + { + if (!condition) + return; + + UnityEngine.Assertions.Assert.IsFalse(condition, message); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreApproximatelyEqual(float expected, float actual) + { + UnityEngine.Assertions.Assert.AreApproximatelyEqual(expected, actual); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreApproximatelyEqual(float expected, float actual, string message) + { + UnityEngine.Assertions.Assert.AreApproximatelyEqual(expected, actual, message); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreApproximatelyEqual(float expected, float actual, float tolerance) + { + UnityEngine.Assertions.Assert.AreApproximatelyEqual(expected, actual, tolerance); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreEqual(T expected, T actual) + { + UnityEngine.Assertions.Assert.AreEqual(expected, actual); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreNotEqual(T expected, T actual) + { + UnityEngine.Assertions.Assert.AreNotEqual(expected, actual); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreEqual(int expected, int actual) + { + if (expected == actual) + return; + + UnityEngine.Assertions.Assert.AreEqual(expected, actual); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreNotEqual(int expected, int actual) + { + if (expected != actual) + return; + + UnityEngine.Assertions.Assert.AreNotEqual(expected, actual); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreEqual(bool expected, bool actual) + { + if (expected == actual) + return; + + UnityEngine.Assertions.Assert.AreEqual(expected, actual); + } + + [Conditional("UNITY_ASSERTIONS")] + public static void AreNotEqual(bool expected, bool actual) + { + if (expected != actual) + return; + + UnityEngine.Assertions.Assert.AreNotEqual(expected, actual); + } + } +} diff --git a/Unity.Entities/Stubs/Unity.Assertions/Assert.cs.meta b/Unity.Entities/Stubs/Unity.Assertions/Assert.cs.meta new file mode 100644 index 00000000..227bcfa9 --- /dev/null +++ b/Unity.Entities/Stubs/Unity.Assertions/Assert.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 703cbb06f0100412da345836409fbcf7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Stubs/Unity.meta b/Unity.Entities/Stubs/Unity.meta new file mode 100644 index 00000000..0ea98aa1 --- /dev/null +++ b/Unity.Entities/Stubs/Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 91a44c865deb94d78ab9c8dcb9cad8a2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Stubs/Unity/Debug.cs b/Unity.Entities/Stubs/Unity/Debug.cs new file mode 100644 index 00000000..e329d3bb --- /dev/null +++ b/Unity.Entities/Stubs/Unity/Debug.cs @@ -0,0 +1,23 @@ +using System; + +namespace Unity +{ + // TODO: provide an implementation of Unity.Debug that does not rely on UnityEngine. + internal static class Debug + { + public static void LogError(object message) + { + UnityEngine.Debug.LogError(message); + } + + public static void Log(object message) + { + UnityEngine.Debug.Log(message); + } + + public static void LogException(Exception exception) + { + UnityEngine.Debug.LogException(exception); + } + } +} diff --git a/Unity.Entities/Stubs/Unity/Debug.cs.meta b/Unity.Entities/Stubs/Unity/Debug.cs.meta new file mode 100644 index 00000000..b43e58c6 --- /dev/null +++ b/Unity.Entities/Stubs/Unity/Debug.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d616b4397faef4d3c9f28c093d4485d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Types.meta b/Unity.Entities/Types.meta new file mode 100644 index 00000000..b5635bcc --- /dev/null +++ b/Unity.Entities/Types.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 94c1ff0cd06e944388c16ac3e891e402 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Types/CalculateReaderWriterDependency.cs b/Unity.Entities/Types/CalculateReaderWriterDependency.cs new file mode 100644 index 00000000..89247de4 --- /dev/null +++ b/Unity.Entities/Types/CalculateReaderWriterDependency.cs @@ -0,0 +1,33 @@ +using Unity.Collections; + +namespace Unity.Entities +{ + internal static class CalculateReaderWriterDependency + { + public static bool Add(ComponentType type, NativeList reading, NativeList writing) + { + if (!type.RequiresJobDependency) + return false; + + if (type.AccessModeType == ComponentType.AccessMode.ReadOnly) + { + if (reading.Contains(type.TypeIndex)) + return false; + if (writing.Contains(type.TypeIndex)) + return false; + + reading.Add(type.TypeIndex); + return true; + } + + var readingIndex = reading.IndexOf(type.TypeIndex); + if (readingIndex != -1) + reading.RemoveAtSwapBack(readingIndex); + if (writing.Contains(type.TypeIndex)) + return false; + + writing.Add(type.TypeIndex); + return true; + } + } +} diff --git a/Unity.Entities/Types/CalculateReaderWriterDependency.cs.meta b/Unity.Entities/Types/CalculateReaderWriterDependency.cs.meta new file mode 100644 index 00000000..572b5847 --- /dev/null +++ b/Unity.Entities/Types/CalculateReaderWriterDependency.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6400c6a7db3124f4aaab8ae5840d39e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Types/ComponentType.cs b/Unity.Entities/Types/ComponentType.cs new file mode 100644 index 00000000..bc13e325 --- /dev/null +++ b/Unity.Entities/Types/ComponentType.cs @@ -0,0 +1,181 @@ +using System; + +namespace Unity.Entities +{ + public struct SubtractiveComponent + { + } + + public struct ComponentType + { + public enum AccessMode + { + ReadWrite, + ReadOnly, + Subtractive + } + + public int TypeIndex; + public AccessMode AccessModeType; + public int FixedArrayLength; + + public static ComponentType Create() + { + return FromTypeIndex(TypeManager.GetTypeIndex()); + } + + public static ComponentType FromTypeIndex(int typeIndex) + { + ComponentType type; + type.TypeIndex = typeIndex; + type.AccessModeType = AccessMode.ReadWrite; + type.FixedArrayLength = -1; + return type; + } + + public static ComponentType ReadOnly(Type type) + { + ComponentType t; + t.TypeIndex = TypeManager.GetTypeIndex(type); + t.AccessModeType = AccessMode.ReadOnly; + t.FixedArrayLength = -1; + return t; + } + + public static ComponentType ReadOnly() + { + ComponentType t; + t.TypeIndex = TypeManager.GetTypeIndex(); + t.AccessModeType = AccessMode.ReadOnly; + t.FixedArrayLength = -1; + return t; + } + + public static ComponentType Subtractive(Type type) + { + ComponentType t; + t.TypeIndex = TypeManager.GetTypeIndex(type); + t.AccessModeType = AccessMode.Subtractive; + t.FixedArrayLength = -1; + return t; + } + + public static ComponentType Subtractive() + { + ComponentType t; + t.TypeIndex = TypeManager.GetTypeIndex(); + t.AccessModeType = AccessMode.Subtractive; + t.FixedArrayLength = -1; + return t; + } + + public ComponentType(Type type, AccessMode accessModeType = AccessMode.ReadWrite) + { + TypeIndex = TypeManager.GetTypeIndex(type); + AccessModeType = accessModeType; + FixedArrayLength = -1; + } + + public static ComponentType FixedArray(Type type, int numElements) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (numElements < 0) + throw new ArgumentException("FixedArray length must be 0 or larger"); +#endif + + ComponentType t; + t.TypeIndex = TypeManager.GetTypeIndex(type); + t.AccessModeType = AccessMode.ReadWrite; + t.FixedArrayLength = numElements; + return t; + } + + internal bool RequiresJobDependency + { + get + { + if (AccessModeType == AccessMode.Subtractive) + return false; + var type = GetManagedType(); + //@TODO: This is wrong... Not right for fixed array, think about Entity array? + return typeof(IComponentData).IsAssignableFrom(type); + } + } + + public Type GetManagedType() + { + return TypeManager.GetType(TypeIndex); + } + + public static implicit operator ComponentType(Type type) + { + return new ComponentType(type, AccessMode.ReadWrite); + } + + public static bool operator <(ComponentType lhs, ComponentType rhs) + { + if (lhs.TypeIndex == rhs.TypeIndex) + return lhs.FixedArrayLength != rhs.FixedArrayLength + ? lhs.FixedArrayLength < rhs.FixedArrayLength + : lhs.AccessModeType < rhs.AccessModeType; + + return lhs.TypeIndex < rhs.TypeIndex; + } + + public static bool operator >(ComponentType lhs, ComponentType rhs) + { + return rhs < lhs; + } + + public static bool operator ==(ComponentType lhs, ComponentType rhs) + { + return lhs.TypeIndex == rhs.TypeIndex && lhs.FixedArrayLength == rhs.FixedArrayLength && + lhs.AccessModeType == rhs.AccessModeType; + } + + public static bool operator !=(ComponentType lhs, ComponentType rhs) + { + return lhs.TypeIndex != rhs.TypeIndex || lhs.FixedArrayLength != rhs.FixedArrayLength || + lhs.AccessModeType != rhs.AccessModeType; + } + + internal static unsafe bool CompareArray(ComponentType* type1, int typeCount1, ComponentType* type2, + int typeCount2) + { + if (typeCount1 != typeCount2) + return false; + for (var i = 0; i < typeCount1; ++i) + if (type1[i] != type2[i]) + return false; + return true; + } + + public bool IsFixedArray => FixedArrayLength != -1; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public override string ToString() + { + var name = GetManagedType().Name; + if (IsFixedArray) + return $"{name}[{FixedArrayLength}]"; + if (AccessModeType == AccessMode.Subtractive) + return $"{name} [S]"; + if (AccessModeType == AccessMode.ReadOnly) + return $"{name} [RO]"; + if (TypeIndex == 0 && FixedArrayLength == 0) + return "None"; + return name; + } +#endif + + public override bool Equals(object obj) + { + return obj is ComponentType && (ComponentType) obj == this; + } + + public override int GetHashCode() + { + return (TypeIndex * 5813) ^ FixedArrayLength; + } + } +} diff --git a/Unity.Entities/Types/ComponentType.cs.meta b/Unity.Entities/Types/ComponentType.cs.meta new file mode 100644 index 00000000..1e88ff9f --- /dev/null +++ b/Unity.Entities/Types/ComponentType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 00b0bff1d67e64dd7a82a3ec89bbdaed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Types/FastEquality.cs b/Unity.Entities/Types/FastEquality.cs new file mode 100644 index 00000000..03dfa074 --- /dev/null +++ b/Unity.Entities/Types/FastEquality.cs @@ -0,0 +1,175 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using Boo.Lang; +using Unity.Collections.LowLevel.Unsafe; + +[assembly: InternalsVisibleTo("Unity.Entities.Tests")] + +namespace Unity.Entities +{ + public static class FastEquality + { + internal static Layout[] CreateLayout(Type type) + { + var begin = 0; + var end = 0; + + var layouts = new List(); + + CreateLayoutRecurse(type, 0, layouts, ref begin, ref end); + + if (begin != end) + layouts.Add(new Layout {offset = begin, count = end - begin, Aligned4 = false}); + + var layoutsArray = layouts.ToArray(); + + for (var i = 0; i != layoutsArray.Length; i++) + if (layoutsArray[i].count % 4 == 0 && layoutsArray[i].offset % 4 == 0) + { + layoutsArray[i].count /= 4; + layoutsArray[i].Aligned4 = true; + } + + return layoutsArray; + } + + public struct Layout + { + public int offset; + public int count; + public bool Aligned4; + + public override string ToString() + { + return $"offset: {offset} count: {count} Aligned4: {Aligned4}"; + } + } + + private unsafe struct PointerSize + { +#pragma warning disable 0169 // "never used" warning + private void* pter; +#pragma warning restore 0169 + } + + private static void CreateLayoutRecurse(Type type, int baseOffset, List layouts, ref int begin, + ref int end) + { + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + + foreach (var field in fields) + { + var offset = baseOffset + UnsafeUtility.GetFieldOffset(field); + + if (field.FieldType.IsPrimitive || field.FieldType.IsPointer || field.FieldType.IsClass) + { + var sizeOf = -1; + if (field.FieldType.IsPointer || field.FieldType.IsClass) + sizeOf = UnsafeUtility.SizeOf(); + else + sizeOf = UnsafeUtility.SizeOf(field.FieldType); + + if (end != offset) + { + layouts.Add(new Layout {offset = begin, count = end - begin, Aligned4 = false}); + begin = offset; + end = offset + sizeOf; + } + else + { + end += sizeOf; + } + } + else + { + CreateLayoutRecurse(field.FieldType, offset, layouts, ref begin, ref end); + } + } + } + + //@TODO: Encode type in hashcode... + + private const int FNV_32_PRIME = 0x01000193; + + public static unsafe int GetHashCode(T lhs, Layout[] layout) where T : struct + { + return GetHashCode(UnsafeUtility.AddressOf(ref lhs), layout); + } + + public static unsafe int GetHashCode(ref T lhs, Layout[] layout) where T : struct + { + return GetHashCode(UnsafeUtility.AddressOf(ref lhs), layout); + } + + public static unsafe int GetHashCode(void* dataPtr, Layout[] layout) + { + var data = (byte*) dataPtr; + uint hash = 0; + + for (var k = 0; k != layout.Length; k++) + if (layout[k].Aligned4) + { + var dataInt = (uint*) (data + layout[k].offset); + var count = layout[k].count; + for (var i = 0; i != count; i++) + { + hash *= FNV_32_PRIME; + hash ^= dataInt[i]; + } + } + else + { + var dataByte = data + layout[k].offset; + var count = layout[k].count; + for (var i = 0; i != count; i++) + { + hash *= FNV_32_PRIME; + hash ^= dataByte[i]; + } + } + + return (int) hash; + } + + public static unsafe bool Equals(T lhs, T rhs, Layout[] layout) where T : struct + { + return Equals(UnsafeUtility.AddressOf(ref lhs), UnsafeUtility.AddressOf(ref rhs), layout); + } + + public static unsafe bool Equals(ref T lhs, ref T rhs, Layout[] layout) where T : struct + { + return Equals(UnsafeUtility.AddressOf(ref lhs), UnsafeUtility.AddressOf(ref rhs), layout); + } + + public static unsafe bool Equals(void* lhsPtr, void* rhsPtr, Layout[] layout) + { + var lhs = (byte*) lhsPtr; + var rhs = (byte*) rhsPtr; + + var same = true; + + for (var k = 0; k != layout.Length; k++) + if (layout[k].Aligned4) + { + var offset = layout[k].offset; + var lhsInt = (uint*) (lhs + offset); + var rhsInt = (uint*) (rhs + offset); + var count = layout[k].count; + for (var i = 0; i != count; i++) + same &= lhsInt[i] == rhsInt[i]; + } + else + { + var offset = layout[k].offset; + var lhsByte = lhs + offset; + var rhsByte = rhs + offset; + var count = layout[k].count; + for (var i = 0; i != count; i++) + same &= lhsByte[i] == rhsByte[i]; + } + + return same; + } + } +} diff --git a/Unity.Entities/Types/FastEquality.cs.meta b/Unity.Entities/Types/FastEquality.cs.meta new file mode 100644 index 00000000..b85c1e83 --- /dev/null +++ b/Unity.Entities/Types/FastEquality.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ddd34bcbf1cf44a8283599c5713372b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Types/TypeManager.cs b/Unity.Entities/Types/TypeManager.cs new file mode 100644 index 00000000..1af4e58a --- /dev/null +++ b/Unity.Entities/Types/TypeManager.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; + +namespace Unity.Entities +{ + public static unsafe class TypeManager + { + public enum TypeCategory + { + ComponentData, + ISharedComponentData, + OtherValueType, + EntityData, + Class + } + + public const int MaximumTypesCount = 1024 * 10; + private static ComponentType[] s_Types; + private static volatile int s_Count; + private static SpinLock s_CreateTypeLock; + public static int ObjectOffset; + internal static readonly Type UnityEngineComponentType = typeof(Component); + + private struct StaticTypeLookup + { + public static int typeIndex; + } + + public struct ComponentType + { + public ComponentType(Type type, int size, TypeCategory category, FastEquality.Layout[] layout) + { + Type = type; + SizeInChunk = size; + Category = category; + FastEqualityLayout = layout; + } + + public readonly Type Type; + public readonly int SizeInChunk; + public readonly FastEquality.Layout[] FastEqualityLayout; + public readonly TypeCategory Category; + } + + // TODO: this creates a dependency on UnityEngine, but makes splitting code in separate assemblies easier. We need to remove it during the biggere refactor. + private struct ObjectOffsetType + { +#pragma warning disable 0169 // "never used" warning + private void* v0; + private void* v1; +#pragma warning restore 0169 + } + + public static void Initialize() + { + if (s_Types != null) + return; + + ObjectOffset = UnsafeUtility.SizeOf(); + s_CreateTypeLock = new SpinLock(); + s_Types = new ComponentType[MaximumTypesCount]; + s_Count = 0; + + s_Types[s_Count++] = new ComponentType(null, 0, TypeCategory.ComponentData, null); + // This must always be first so that Entity is always index 0 in the archetype + s_Types[s_Count++] = new ComponentType(typeof(Entity), sizeof(Entity), TypeCategory.EntityData, + FastEquality.CreateLayout(typeof(Entity))); + } + + + public static int GetTypeIndex() + { + var typeIndex = StaticTypeLookup.typeIndex; + if (typeIndex != 0) + return typeIndex; + + typeIndex = GetTypeIndex(typeof(T)); + StaticTypeLookup.typeIndex = typeIndex; + return typeIndex; + } + + public static int GetTypeIndex(Type type) + { + var index = FindTypeIndex(type, s_Count); + return index != -1 ? index : CreateTypeIndexThreadSafe(type); + } + + private static int FindTypeIndex(Type type, int count) + { + for (var i = 0; i != count; i++) + { + var c = s_Types[i]; + if (c.Type == type) + return i; + } + + return -1; + } + +#if UNITY_EDITOR + public static int TypesCount => s_Count; + + public static IEnumerable AllTypes() + { + return Enumerable.Take(s_Types, s_Count); + } +#endif //UNITY_EDITOR + + private static int CreateTypeIndexThreadSafe(Type type) + { + var lockTaken = false; + try + { + s_CreateTypeLock.Enter(ref lockTaken); + + // After taking the lock, make sure the type hasn't been created + // after doing the non-atomic FindTypeIndex + var index = FindTypeIndex(type, s_Count); + if (index != -1) + return index; + + var componentType = BuildComponentType(type); + + index = s_Count++; + s_Types[index] = componentType; + + return index; + } + finally + { + if (lockTaken) + s_CreateTypeLock.Exit(true); + } + } + + private static ComponentType BuildComponentType(Type type) + { + var componentSize = 0; + TypeCategory category; + FastEquality.Layout[] fastEqualityLayout = null; + if (typeof(IComponentData).IsAssignableFrom(type)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (type.IsClass) + throw new ArgumentException($"{type} is an IComponentData, and thus must be a struct."); + if (!UnsafeUtility.IsBlittable(type)) + throw new ArgumentException( + $"{type} is an IComponentData, and thus must be blittable (No managed object is allowed on the struct)."); +#endif + + category = TypeCategory.ComponentData; + componentSize = UnsafeUtility.SizeOf(type); + fastEqualityLayout = FastEquality.CreateLayout(type); + } + else if (typeof(ISharedComponentData).IsAssignableFrom(type)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (type.IsClass) + throw new ArgumentException($"{type} is an ISharedComponentData, and thus must be a struct."); +#endif + + category = TypeCategory.ISharedComponentData; + fastEqualityLayout = FastEquality.CreateLayout(type); + } + else if (type.IsValueType) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!UnsafeUtility.IsBlittable(type)) + throw new ArgumentException($"{type} is used for FixedArrays, and thus must be blittable."); +#endif + category = TypeCategory.OtherValueType; + componentSize = UnsafeUtility.SizeOf(type); + } + else if (type.IsClass) + { + category = TypeCategory.Class; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (type.FullName == "Unity.Entities.GameObjectEntity") + throw new ArgumentException( + "GameObjectEntity can not be used from EntityManager. The component is ignored when creating entities for a GameObject."); +#endif + } +#if ENABLE_UNITY_COLLECTIONS_CHECKS + else + { + throw new ArgumentException($"'{type}' is not a valid component"); + } +#else + else + { + category = TypeCategory.OtherValueType; + } +#endif + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (typeof(IComponentData).IsAssignableFrom(type) && typeof(ISharedComponentData).IsAssignableFrom(type)) + throw new ArgumentException($"Component {type} can not be both IComponentData & ISharedComponentData"); +#endif + return new ComponentType(type, componentSize, category, fastEqualityLayout); + } + + public static bool IsValidComponentTypeForArchetype(int typeIndex, bool isArray) + { + if (s_Types[typeIndex].Category == TypeCategory.OtherValueType) + return isArray; + return !isArray; + } + + public static ComponentType GetComponentType(int typeIndex) + { + return s_Types[typeIndex]; + } + + public static ComponentType GetComponentType() + { + return s_Types[GetTypeIndex()]; + } + + public static Type GetType(int typeIndex) + { + return s_Types[typeIndex].Type; + } + + public static int GetTypeCount() + { + return s_Count; + } + } +} diff --git a/Unity.Entities/Types/TypeManager.cs.meta b/Unity.Entities/Types/TypeManager.cs.meta new file mode 100644 index 00000000..cee1b203 --- /dev/null +++ b/Unity.Entities/Types/TypeManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77f9b3d85ce78488ea16f17c1458fb5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Types/VoidSystemComponent.cs b/Unity.Entities/Types/VoidSystemComponent.cs new file mode 100644 index 00000000..f29f6f86 --- /dev/null +++ b/Unity.Entities/Types/VoidSystemComponent.cs @@ -0,0 +1,10 @@ +namespace Unity.Entities +{ + /// + /// Any associated components are ignored by the TSystem. + /// + public struct VoidSystem : IComponentData + where TSystem : ComponentSystemBase + { + } +} diff --git a/Unity.Entities/Types/VoidSystemComponent.cs.meta b/Unity.Entities/Types/VoidSystemComponent.cs.meta new file mode 100644 index 00000000..5ea96ac0 --- /dev/null +++ b/Unity.Entities/Types/VoidSystemComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf4c5a2deb7984eb48809ae37f230f97 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/Unity.Entities.asmdef b/Unity.Entities/Unity.Entities.asmdef new file mode 100644 index 00000000..766d254d --- /dev/null +++ b/Unity.Entities/Unity.Entities.asmdef @@ -0,0 +1,12 @@ +{ + "name": "Unity.Entities", + "references": [ + "Unity.Collections", + "Unity.Burst", + "Unity.Mathematics" + ], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true +} diff --git a/Unity.Entities/Unity.Entities.asmdef.meta b/Unity.Entities/Unity.Entities.asmdef.meta new file mode 100644 index 00000000..51d76cc9 --- /dev/null +++ b/Unity.Entities/Unity.Entities.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 734d92eba21c94caba915361bd5ac177 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Entities/UnsafeLinkedListNode.cs b/Unity.Entities/UnsafeLinkedListNode.cs new file mode 100644 index 00000000..865e7ded --- /dev/null +++ b/Unity.Entities/UnsafeLinkedListNode.cs @@ -0,0 +1,98 @@ +using Unity.Assertions; + +namespace Unity.Entities +{ + // IMPORTANT NOTE: + // UnsafeLinkedListNode may NOT be put into any memory owned by a class. + // The memory containing it must ALWAYS be allocated with malloc instead, also it can never be on the stack. + internal unsafe struct UnsafeLinkedListNode + { + public UnsafeLinkedListNode* Prev; + public UnsafeLinkedListNode* Next; + + public static void InitializeList(UnsafeLinkedListNode* list) + { + list->Prev = list; + list->Next = list; + } + + public bool IsInList => Prev != null; + + public UnsafeLinkedListNode* Begin => Next; + + public UnsafeLinkedListNode* Back => Prev; + + public bool IsEmpty + { + get + { + fixed (UnsafeLinkedListNode* list = &this) + { + return list == Next; + } + } + } + + public UnsafeLinkedListNode* End + { + get + { + fixed (UnsafeLinkedListNode* list = &this) + { + return list; + } + } + } + + public void Add(UnsafeLinkedListNode* node) + { + fixed (UnsafeLinkedListNode* list = &this) + { + InsertBefore(list, node); + } + } + + public static void InsertBefore(UnsafeLinkedListNode* pos, UnsafeLinkedListNode* node) + { + Assert.IsTrue(node != pos); + Assert.IsFalse(node->IsInList); + + node->Prev = pos->Prev; + node->Next = pos; + + node->Prev->Next = node; + node->Next->Prev = node; + } + + public static void InsertListBefore(UnsafeLinkedListNode* pos, UnsafeLinkedListNode* srcList) + { + Assert.IsTrue(pos != srcList); + Assert.IsFalse(srcList->IsEmpty); + + // Insert source before pos + var a = pos->Prev; + var b = pos; + a->Next = srcList->Next; + b->Prev = srcList->Prev; + a->Next->Prev = a; + b->Prev->Next = b; + + // Clear source list + srcList->Next = srcList; + srcList->Prev = srcList; + } + + public void Remove() + { + if (Prev == null) + return; + + Prev->Next = Next; + Next->Prev = Prev; + Prev = null; + Next = null; + } + } + + // it takes pointers to other nodes and thus can't handle a moving GC if the data was on a class +} diff --git a/Unity.Entities/UnsafeLinkedListNode.cs.meta b/Unity.Entities/UnsafeLinkedListNode.cs.meta new file mode 100644 index 00000000..ed0c7415 --- /dev/null +++ b/Unity.Entities/UnsafeLinkedListNode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 45c98b5cbfee412193130a99a0840692 +timeCreated: 1513449131 \ No newline at end of file diff --git a/Unity.Rendering.Hybrid.meta b/Unity.Rendering.Hybrid.meta new file mode 100644 index 00000000..d0a1df68 --- /dev/null +++ b/Unity.Rendering.Hybrid.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 9a174a67e29734f8ea02d09441fb0217 +folderAsset: yes +timeCreated: 1508051439 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Rendering.Hybrid/MeshInstanceRendererComponent.cs b/Unity.Rendering.Hybrid/MeshInstanceRendererComponent.cs new file mode 100644 index 00000000..a6b2d258 --- /dev/null +++ b/Unity.Rendering.Hybrid/MeshInstanceRendererComponent.cs @@ -0,0 +1,24 @@ +using System; +using Unity.Entities; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Unity.Rendering +{ + /// + /// Render Mesh with Material (must be instanced material) by object to world matrix. + /// Specified by TransformMatrix associated with Entity. + /// + [Serializable] + public struct MeshInstanceRenderer : ISharedComponentData + { + public Mesh mesh; + public Material material; + public int subMesh; + + public ShadowCastingMode castShadows; + public bool receiveShadows; + } + + public class MeshInstanceRendererComponent : SharedComponentDataWrapper { } +} diff --git a/Unity.Rendering.Hybrid/MeshInstanceRendererComponent.cs.meta b/Unity.Rendering.Hybrid/MeshInstanceRendererComponent.cs.meta new file mode 100644 index 00000000..5d372563 --- /dev/null +++ b/Unity.Rendering.Hybrid/MeshInstanceRendererComponent.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9b0fd4427893a4a16ba0c267dfd00217 +timeCreated: 1497278756 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 100 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Rendering.Hybrid/MeshInstanceRendererSystem.cs b/Unity.Rendering.Hybrid/MeshInstanceRendererSystem.cs new file mode 100644 index 00000000..dc9a4c80 --- /dev/null +++ b/Unity.Rendering.Hybrid/MeshInstanceRendererSystem.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine; +using UnityEngine.Assertions; +using UnityEngine.Experimental.PlayerLoop; + +namespace Unity.Rendering +{ + /// + /// Renders all Entities containing both MeshInstanceRenderer & TransformMatrix components. + /// + [UpdateAfter(typeof(PreLateUpdate.ParticleSystemBeginUpdateAll))] + [UnityEngine.ExecuteInEditMode] + public class MeshInstanceRendererSystem : ComponentSystem + { + // Instance renderer takes only batches of 1023 + Matrix4x4[] m_MatricesArray = new Matrix4x4[1023]; + List m_CacheduniqueRendererTypes = new List(10); + ComponentGroup m_InstanceRendererGroup; + + // This is the ugly bit, necessary until Graphics.DrawMeshInstanced supports NativeArrays pulling the data in from a job. + public unsafe static void CopyMatrices(ComponentDataArray transforms, int beginIndex, int length, Matrix4x4[] outMatrices) + { + // @TODO: This is using unsafe code because the Unity DrawInstances API takes a Matrix4x4[] instead of NativeArray. + // We want to use the ComponentDataArray.CopyTo method + // because internally it uses memcpy to copy the data, + // if the nativeslice layout matches the layout of the component data. It's very fast... + fixed (Matrix4x4* matricesPtr = outMatrices) + { + Assert.AreEqual(sizeof(Matrix4x4), sizeof(TransformMatrix)); + var matricesSlice = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice(matricesPtr, sizeof(Matrix4x4), length); + #if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeSliceUnsafeUtility.SetAtomicSafetyHandle(ref matricesSlice, AtomicSafetyHandle.GetTempUnsafePtrSliceHandle()); + #endif + transforms.CopyTo(matricesSlice, beginIndex); + } + } + + protected override void OnCreateManager(int capacity) + { + // We want to find all MeshInstanceRenderer & TransformMatrix combinations and render them + m_InstanceRendererGroup = GetComponentGroup(typeof(MeshInstanceRenderer), typeof(TransformMatrix)); + } + + protected override void OnUpdate() + { + // We want to iterate over all unique MeshInstanceRenderer shared component data, + // that are attached to any entities in the world + EntityManager.GetAllUniqueSharedComponentDatas(m_CacheduniqueRendererTypes); + var forEachFilter = m_InstanceRendererGroup.CreateForEachFilter(m_CacheduniqueRendererTypes); + + for (int i = 0;i != m_CacheduniqueRendererTypes.Count;i++) + { + // For each unique MeshInstanceRenderer data, we want to get all entities with a TransformMatrix + // SharedComponentData gurantees that all those entities are packed togehter in a chunk with linear memory layout. + // As a result the copy of the matrices out is internally done via memcpy. + var renderer = m_CacheduniqueRendererTypes[i]; + var transforms = m_InstanceRendererGroup.GetComponentDataArray(forEachFilter, i); + + // Graphics.DrawMeshInstanced has a set of limitations that are not optimal for working with ECS. + // Specifically: + // * No way to push the matrices from a job + // * no NativeArray API, currently uses Matrix4x4[] + // As a result this code is not yet jobified. + // We are planning to adjust this API to make it more efficient for this use case. + + // For now, we have to copy our data into Matrix4x4[] with a specific upper limit of how many instances we can render in one batch. + // So we just have a for loop here, representing each Graphics.DrawMeshInstanced batch + int beginIndex = 0; + while (beginIndex < transforms.Length) + { + int length = math.min(m_MatricesArray.Length, transforms.Length - beginIndex); + CopyMatrices(transforms, beginIndex, length, m_MatricesArray); + Graphics.DrawMeshInstanced(renderer.mesh, renderer.subMesh, renderer.material, m_MatricesArray, length, null, renderer.castShadows, renderer.receiveShadows); + + beginIndex += length; + } + } + + m_CacheduniqueRendererTypes.Clear(); + forEachFilter.Dispose(); + } + } +} diff --git a/Unity.Rendering.Hybrid/MeshInstanceRendererSystem.cs.meta b/Unity.Rendering.Hybrid/MeshInstanceRendererSystem.cs.meta new file mode 100644 index 00000000..440a849f --- /dev/null +++ b/Unity.Rendering.Hybrid/MeshInstanceRendererSystem.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bd123d4d8403c4386b9614951dde6da8 +timeCreated: 1497282098 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Rendering.Hybrid/Unity.Rendering.Hybrid.asmdef b/Unity.Rendering.Hybrid/Unity.Rendering.Hybrid.asmdef new file mode 100644 index 00000000..54086cb0 --- /dev/null +++ b/Unity.Rendering.Hybrid/Unity.Rendering.Hybrid.asmdef @@ -0,0 +1,16 @@ +{ + "name": "Unity.Rendering.Hybrid", + "references": [ + "Unity.Entities", + "Unity.Entities.Hybrid", + "Unity.Transforms", + "Unity.Collections", + "Unity.Burst", + "Unity.Jobs", + "Unity.Mathematics" + ], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true +} diff --git a/Unity.Rendering.Hybrid/Unity.Rendering.Hybrid.asmdef.meta b/Unity.Rendering.Hybrid/Unity.Rendering.Hybrid.asmdef.meta new file mode 100644 index 00000000..59cfb4cb --- /dev/null +++ b/Unity.Rendering.Hybrid/Unity.Rendering.Hybrid.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7a450cf7ca9694b5a8bfa3fd83ec635a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.Hybrid.meta b/Unity.Transforms.Hybrid.meta new file mode 100644 index 00000000..50ed7d43 --- /dev/null +++ b/Unity.Transforms.Hybrid.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: eeb8550e905444989e968ee284969334 +timeCreated: 1517722607 \ No newline at end of file diff --git a/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectComponent.cs b/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectComponent.cs new file mode 100644 index 00000000..21bc33a4 --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectComponent.cs @@ -0,0 +1,12 @@ +using Unity.Entities; + +namespace Unity.Transforms +{ + /// + /// Copy Transform from GameObject associated with Entity to TransformMatrix. + /// Once only. Component is removed after copy. + /// + public struct CopyInitialTransformFromGameObject : IComponentData { } + + public class CopyInitialTransformFromGameObjectComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectComponent.cs.meta b/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectComponent.cs.meta new file mode 100644 index 00000000..a48f420d --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1f45258a2afd45afabd0d78ae4fcf414 +timeCreated: 1517610468 \ No newline at end of file diff --git a/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectSystem.cs b/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectSystem.cs new file mode 100644 index 00000000..5a72f061 --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectSystem.cs @@ -0,0 +1,134 @@ +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine.Jobs; + +namespace Unity.Transforms +{ + public class CopyInitialTransformFromGameObjectSystem : JobComponentSystem + { + [Inject] ComponentDataFromEntity m_LocalPositions; + [Inject] ComponentDataFromEntity m_LocalRotations; + [Inject] ComponentDataFromEntity m_Positions; + [Inject] ComponentDataFromEntity m_Rotations; + + struct TransformStash + { + public float3 localPosition; + public float3 position; + public quaternion localRotation; + public quaternion rotation; + } + + [ComputeJobOptimization] + struct StashTransforms : IJobParallelForTransform + { + public NativeArray transformStashes; + + public void Execute(int index, TransformAccess transform) + { + transformStashes[index] = new TransformStash + { + localPosition = transform.localPosition, + rotation = transform.rotation, + position = transform.position, + localRotation = transform.localRotation, + }; + } + } + + [ComputeJobOptimization] + struct CopyTransforms : IJobParallelFor + { + [NativeDisableParallelForRestriction] public ComponentDataFromEntity localPositions; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity localRotations; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity positions; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity rotations; + [ReadOnly] + public EntityArray entities; + [DeallocateOnJobCompletion] public NativeArray transformStashes; + + public void Execute(int index) + { + var transformStash = transformStashes[index]; + var entity = entities[index]; + if (positions.Exists(entity)) + { + positions[entity] = new Position { Value = transformStash.position }; + } + if (rotations.Exists(entity)) + { + rotations[entity] = new Rotation { Value = transformStash.rotation }; + } + if (localPositions.Exists(entity)) + { + localPositions[entity] = new LocalPosition { Value = transformStash.localPosition }; + } + if (localRotations.Exists(entity)) + { + localRotations[entity] = new LocalRotation { Value = transformStash.localRotation }; + } + } + } + + struct RemoveCopyInitialTransformFromGameObjectComponent : IJob + { + [ReadOnly] + public EntityArray entities; + public EntityCommandBuffer entityCommandBuffer; + + public void Execute() + { + for (int i = 0; i < entities.Length; i++) + { + entityCommandBuffer.RemoveComponent(entities[i]); + } + + } + } + + [Inject] private EndFrameBarrier m_EndFrameBarrier; + + ComponentGroup m_InitialTransformGroup; + + protected override void OnCreateManager(int capacity) + { + m_InitialTransformGroup = GetComponentGroup(ComponentType.ReadOnly(typeof(CopyInitialTransformFromGameObject)),typeof(UnityEngine.Transform)); + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + var transforms = m_InitialTransformGroup.GetTransformAccessArray(); + var entities = m_InitialTransformGroup.GetEntityArray(); + + var transformStashes = new NativeArray(transforms.length, Allocator.TempJob); + var stashTransformsJob = new StashTransforms + { + transformStashes = transformStashes + }; + + var stashTransformsJobHandle = stashTransformsJob.Schedule(transforms, inputDeps); + + var copyTransformsJob = new CopyTransforms + { + positions = m_Positions, + rotations = m_Rotations, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + transformStashes = transformStashes, + entities = entities + }; + + var copyTransformsJobHandle = copyTransformsJob.Schedule(transformStashes.Length,64,stashTransformsJobHandle); + + var removeComponentsJob = new RemoveCopyInitialTransformFromGameObjectComponent + { + entities = entities, + entityCommandBuffer = m_EndFrameBarrier.CreateCommandBuffer() + }; + var removeComponentsJobHandle = removeComponentsJob.Schedule(copyTransformsJobHandle); + return removeComponentsJobHandle; + } + } +} diff --git a/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectSystem.cs.meta b/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectSystem.cs.meta new file mode 100644 index 00000000..a7e3f44e --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyInitialTransformFromGameObjectSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6ead4efdd28745858c0affad95a01ffc +timeCreated: 1517610704 \ No newline at end of file diff --git a/Unity.Transforms.Hybrid/CopyTransformFromGameObjectComponent.cs b/Unity.Transforms.Hybrid/CopyTransformFromGameObjectComponent.cs new file mode 100644 index 00000000..044a91e9 --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyTransformFromGameObjectComponent.cs @@ -0,0 +1,11 @@ +using Unity.Entities; + +namespace Unity.Transforms +{ + /// + /// Copy Transform from GameObject associated with Entity to TransformMatrix. + /// + public struct CopyTransformFromGameObject : IComponentData { } + + public class CopyTransformFromGameObjectComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms.Hybrid/CopyTransformFromGameObjectComponent.cs.meta b/Unity.Transforms.Hybrid/CopyTransformFromGameObjectComponent.cs.meta new file mode 100644 index 00000000..2e58e052 --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyTransformFromGameObjectComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d8133af023214ae993fc28d2fd97a1e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.Hybrid/CopyTransformFromGameObjectSystem.cs b/Unity.Transforms.Hybrid/CopyTransformFromGameObjectSystem.cs new file mode 100644 index 00000000..2c95e9c2 --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyTransformFromGameObjectSystem.cs @@ -0,0 +1,107 @@ +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine.Jobs; + +namespace Unity.Transforms +{ + public class CopyTransformFromGameObjectSystem : JobComponentSystem + { + [Inject] ComponentDataFromEntity m_LocalPositions; + [Inject] ComponentDataFromEntity m_LocalRotations; + [Inject] ComponentDataFromEntity m_Positions; + [Inject] ComponentDataFromEntity m_Rotations; + + struct TransformStash + { + public float3 localPosition; + public float3 position; + public quaternion localRotation; + public quaternion rotation; + } + + [ComputeJobOptimization] + struct StashTransforms : IJobParallelForTransform + { + public NativeArray transformStashes; + + public void Execute(int index, TransformAccess transform) + { + transformStashes[index] = new TransformStash + { + localPosition = transform.localPosition, + rotation = transform.rotation, + position = transform.position, + localRotation = transform.localRotation, + }; + } + } + + [ComputeJobOptimization] + struct CopyTransforms : IJobParallelFor + { + [NativeDisableParallelForRestriction] public ComponentDataFromEntity localPositions; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity localRotations; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity positions; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity rotations; + [ReadOnly] public EntityArray entities; + [DeallocateOnJobCompletion] public NativeArray transformStashes; + + public void Execute(int index) + { + var transformStash = transformStashes[index]; + var entity = entities[index]; + if (positions.Exists(entity)) + { + positions[entity] = new Position { Value = transformStash.position }; + } + if (rotations.Exists(entity)) + { + rotations[entity] = new Rotation { Value = transformStash.rotation }; + } + if (localPositions.Exists(entity)) + { + localPositions[entity] = new LocalPosition { Value = transformStash.localPosition }; + } + if (localRotations.Exists(entity)) + { + localRotations[entity] = new LocalRotation { Value = transformStash.localRotation }; + } + } + } + + ComponentGroup m_TransformGroup; + + protected override void OnCreateManager(int capacity) + { + m_TransformGroup = GetComponentGroup(ComponentType.ReadOnly(typeof(CopyTransformFromGameObject)),typeof(UnityEngine.Transform)); + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + var transforms = m_TransformGroup.GetTransformAccessArray(); + var entities = m_TransformGroup.GetEntityArray(); + + var transformStashes = new NativeArray(transforms.length, Allocator.TempJob); + var stashTransformsJob = new StashTransforms + { + transformStashes = transformStashes + }; + + var stashTransformsJobHandle = stashTransformsJob.Schedule(transforms, inputDeps); + + var copyTransformsJob = new CopyTransforms + { + positions = m_Positions, + rotations = m_Rotations, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + transformStashes = transformStashes, + entities = entities + }; + + return copyTransformsJob.Schedule(transformStashes.Length,64,stashTransformsJobHandle); + } + } +} diff --git a/Unity.Transforms.Hybrid/CopyTransformFromGameObjectSystem.cs.meta b/Unity.Transforms.Hybrid/CopyTransformFromGameObjectSystem.cs.meta new file mode 100644 index 00000000..76e3ba1c --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyTransformFromGameObjectSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3729cb4bf7d742068f502bff414a943 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.Hybrid/CopyTransformToGameObjectComponent.cs b/Unity.Transforms.Hybrid/CopyTransformToGameObjectComponent.cs new file mode 100644 index 00000000..1a634a74 --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyTransformToGameObjectComponent.cs @@ -0,0 +1,11 @@ +using Unity.Entities; + +namespace Unity.Transforms +{ + /// + /// Copy Transform to GameObject associated with Entity from TransformMatrix. + /// + public struct CopyTransformToGameObject : IComponentData { } + + public class CopyTransformToGameObjectComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms.Hybrid/CopyTransformToGameObjectComponent.cs.meta b/Unity.Transforms.Hybrid/CopyTransformToGameObjectComponent.cs.meta new file mode 100644 index 00000000..b158cc24 --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyTransformToGameObjectComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f922032aad1e4f109e892944af196e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.Hybrid/CopyTransformToGameObjectSystem.cs b/Unity.Transforms.Hybrid/CopyTransformToGameObjectSystem.cs new file mode 100644 index 00000000..cbd1194f --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyTransformToGameObjectSystem.cs @@ -0,0 +1,70 @@ +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using UnityEngine.Jobs; + +namespace Unity.Transforms +{ + public class CopyTransformToGameObjectSystem : JobComponentSystem + { + [Inject] [ReadOnly] ComponentDataFromEntity m_Positions; + [Inject] [ReadOnly] ComponentDataFromEntity m_Rotations; + + [Inject] [ReadOnly] ComponentDataFromEntity m_LocalPositions; + [Inject] [ReadOnly] ComponentDataFromEntity m_LocalRotations; + + [ComputeJobOptimization] + struct CopyTransforms : IJobParallelForTransform + { + [ReadOnly] public ComponentDataFromEntity positions; + [ReadOnly] public ComponentDataFromEntity rotations; + + [ReadOnly] public ComponentDataFromEntity localPositions; + [ReadOnly] public ComponentDataFromEntity localRotations; + + [ReadOnly] + public EntityArray entities; + + public void Execute(int index, TransformAccess transform) + { + var entity = entities[index]; + + if (localPositions.Exists(entity)) + transform.localPosition= localPositions[entity].Value; + else if (positions.Exists(entity)) + transform.position = positions[entity].Value; + + if (localRotations.Exists(entity)) + transform.localRotation = localRotations[entity].Value; + else if (rotations.Exists(entity)) + transform.rotation = rotations[entity].Value; + } + } + + ComponentGroup m_TransformGroup; + + protected override void OnCreateManager(int capacity) + { + m_TransformGroup = GetComponentGroup(ComponentType.ReadOnly(typeof(CopyTransformToGameObject)),typeof(UnityEngine.Transform)); + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + var transforms = m_TransformGroup.GetTransformAccessArray(); + var entities = m_TransformGroup.GetEntityArray(); + + var copyTransformsJob = new CopyTransforms + { + positions = m_Positions, + rotations = m_Rotations, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + entities = entities + }; + + var resultDeps = copyTransformsJob.Schedule(transforms,inputDeps); + + return resultDeps; + } + } +} diff --git a/Unity.Transforms.Hybrid/CopyTransformToGameObjectSystem.cs.meta b/Unity.Transforms.Hybrid/CopyTransformToGameObjectSystem.cs.meta new file mode 100644 index 00000000..36c7f204 --- /dev/null +++ b/Unity.Transforms.Hybrid/CopyTransformToGameObjectSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a623fe8c709bf49c9876c5d89f9bca55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.Hybrid/Unity.Transforms.Hybrid.asmdef b/Unity.Transforms.Hybrid/Unity.Transforms.Hybrid.asmdef new file mode 100644 index 00000000..6af2bb35 --- /dev/null +++ b/Unity.Transforms.Hybrid/Unity.Transforms.Hybrid.asmdef @@ -0,0 +1,15 @@ +{ + "name": "Unity.Transforms.Hybrid", + "references": [ + "Unity.Entities", + "Unity.Entities.Hybrid", + "Unity.Transforms", + "Unity.Collections", + "Unity.Burst", + "Unity.Jobs", + "Unity.Mathematics" + ], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [] +} diff --git a/Unity.Transforms.Hybrid/Unity.Transforms.Hybrid.asmdef.meta b/Unity.Transforms.Hybrid/Unity.Transforms.Hybrid.asmdef.meta new file mode 100644 index 00000000..46cdbfdd --- /dev/null +++ b/Unity.Transforms.Hybrid/Unity.Transforms.Hybrid.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 44dcb2bf5b9be4b5aa76674c8edcd1d5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.Tests.meta b/Unity.Transforms.Tests.meta new file mode 100644 index 00000000..2118b1aa --- /dev/null +++ b/Unity.Transforms.Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b873a17ad18c84f60b073703a90e56b0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.Tests/TransformTests.cs b/Unity.Transforms.Tests/TransformTests.cs new file mode 100644 index 00000000..d1402363 --- /dev/null +++ b/Unity.Transforms.Tests/TransformTests.cs @@ -0,0 +1,32 @@ +using NUnit.Framework; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine; + +namespace Unity.Entities.Tests +{ + [TestFixture] + public class TransformTests : ECSTestsFixture + { + [Test] + [Ignore("Likely bug in TransformSystem")] + public void LocalSpaceToGlobalSpace() + { + var parent = m_Manager.CreateEntity(typeof(Position), typeof(LocalPosition), typeof(LocalRotation), typeof(Rotation), typeof(TransformParent)); + var child = m_Manager.CreateEntity(typeof(Position), typeof(LocalPosition), typeof(LocalRotation), typeof(Rotation), typeof(TransformParent)); + + m_Manager.SetComponentData(parent, new LocalPosition(new float3(1, 2, 3))); + m_Manager.SetComponentData(parent, new LocalRotation(quaternion.identity)); + + m_Manager.SetComponentData(child, new TransformParent { Value = parent }); + + m_Manager.SetComponentData(child, new LocalPosition(new float3(4, 5, 6))); + m_Manager.SetComponentData(child, new LocalRotation(quaternion.identity)); + + World.GetOrCreateManager().Update(); + + //@TODO: check all component values... + Assert.AreEqual(new float3(5, 7, 9), m_Manager.GetComponentData(child).Value); + } + } +} diff --git a/Unity.Transforms.Tests/TransformTests.cs.meta b/Unity.Transforms.Tests/TransformTests.cs.meta new file mode 100644 index 00000000..3ce05acf --- /dev/null +++ b/Unity.Transforms.Tests/TransformTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 652b1a688e8e746a7bd7b7e979a9e3a1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.Tests/Unity.Transforms.Tests.asmdef b/Unity.Transforms.Tests/Unity.Transforms.Tests.asmdef new file mode 100644 index 00000000..a3b035b8 --- /dev/null +++ b/Unity.Transforms.Tests/Unity.Transforms.Tests.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Unity.Transforms.Tests", + "references": [ + "Unity.Transforms", + "Unity.Entities", + "Unity.Entities.Tests", + "Unity.Collections", + "Unity.Burst", + "Unity.Jobs", + "Unity.Mathematics" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [] +} diff --git a/Unity.Transforms.Tests/Unity.Transforms.Tests.asmdef.meta b/Unity.Transforms.Tests/Unity.Transforms.Tests.asmdef.meta new file mode 100644 index 00000000..006d78f4 --- /dev/null +++ b/Unity.Transforms.Tests/Unity.Transforms.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c1baa1484221840fa99c34120fb36ae0 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms.meta b/Unity.Transforms.meta new file mode 100644 index 00000000..834b807d --- /dev/null +++ b/Unity.Transforms.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6390c84139cf42ff89c548d610db31b5 +timeCreated: 1516900819 \ No newline at end of file diff --git a/Unity.Transforms/HeadingComponent.cs b/Unity.Transforms/HeadingComponent.cs new file mode 100644 index 00000000..f7bc763e --- /dev/null +++ b/Unity.Transforms/HeadingComponent.cs @@ -0,0 +1,19 @@ +using System; +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms +{ + [Serializable] + public struct Heading : IComponentData + { + public float3 Value; + + public Heading(float3 heading) + { + Value = heading; + } + } + + public class HeadingComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms/HeadingComponent.cs.meta b/Unity.Transforms/HeadingComponent.cs.meta new file mode 100644 index 00000000..1123396b --- /dev/null +++ b/Unity.Transforms/HeadingComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 206d9b88d9fd4c4c9e5a1a976224e421 +timeCreated: 1517513074 \ No newline at end of file diff --git a/Unity.Transforms/HeadingSystem.cs b/Unity.Transforms/HeadingSystem.cs new file mode 100644 index 00000000..45c8d6bb --- /dev/null +++ b/Unity.Transforms/HeadingSystem.cs @@ -0,0 +1,76 @@ +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Unity.Transforms +{ + [UpdateAfter(typeof(TransformInputBarrier))] + [UpdateBefore(typeof(TransformSystem))] + public class HeadingSystem : JobComponentSystem + { + struct HeadingsGroup + { + public ComponentDataArray rotations; + [ReadOnly] public ComponentDataArray headings; + public int Length; + } + + [Inject] private HeadingsGroup m_HeadingsGroup; + + struct LocalHeadingsGroup + { + public ComponentDataArray rotations; + [ReadOnly] public ComponentDataArray headings; + public int Length; + } + + [Inject] private LocalHeadingsGroup m_LocalHeadingsGroup; + + [ComputeJobOptimization] + struct RotationFromHeading : IJobParallelFor + { + public ComponentDataArray rotations; + [ReadOnly] public ComponentDataArray headings; + + public void Execute(int i) + { + var heading = headings[i].Value; + var rotation = math.lookRotationToQuaternion(heading, math.up()); + rotations[i] = new Rotation { Value = rotation }; + } + } + + [ComputeJobOptimization] + struct LocalRotationFromLocalHeading : IJobParallelFor + { + public ComponentDataArray rotations; + [ReadOnly] public ComponentDataArray headings; + + public void Execute(int i) + { + rotations[i] = new LocalRotation { Value = math.lookRotationToQuaternion(headings[i].Value, math.up()) }; + } + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + var rotationFromHeadingJob = new RotationFromHeading + { + rotations = m_HeadingsGroup.rotations, + headings = m_HeadingsGroup.headings, + }; + var rotationFromHeadingJobHandle = rotationFromHeadingJob.Schedule(m_HeadingsGroup.Length, 64, inputDeps); + + var localRotationFromLocalHeadingJob = new LocalRotationFromLocalHeading + { + rotations = m_LocalHeadingsGroup.rotations, + headings = m_LocalHeadingsGroup.headings, + }; + var localRotationFromLocalHeadingJobHandle = localRotationFromLocalHeadingJob.Schedule(m_LocalHeadingsGroup.Length, 64, inputDeps); + + return JobHandle.CombineDependencies(rotationFromHeadingJobHandle,localRotationFromLocalHeadingJobHandle); + } + } +} diff --git a/Unity.Transforms/HeadingSystem.cs.meta b/Unity.Transforms/HeadingSystem.cs.meta new file mode 100644 index 00000000..a60f4dee --- /dev/null +++ b/Unity.Transforms/HeadingSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d6fcba7f85d34a3580235c083cf0105a +timeCreated: 1517513154 \ No newline at end of file diff --git a/Unity.Transforms/LocalHeadingComponent.cs b/Unity.Transforms/LocalHeadingComponent.cs new file mode 100644 index 00000000..adcdd837 --- /dev/null +++ b/Unity.Transforms/LocalHeadingComponent.cs @@ -0,0 +1,14 @@ +using System; +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms +{ + [Serializable] + public struct LocalHeading : IComponentData + { + public float3 Value; + } + + public class LocalHeadingComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms/LocalHeadingComponent.cs.meta b/Unity.Transforms/LocalHeadingComponent.cs.meta new file mode 100644 index 00000000..1e1f2eda --- /dev/null +++ b/Unity.Transforms/LocalHeadingComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 14e543a7d74e4081a4dc42534f3d5624 +timeCreated: 1517986456 \ No newline at end of file diff --git a/Unity.Transforms/LocalPositionComponent.cs b/Unity.Transforms/LocalPositionComponent.cs new file mode 100644 index 00000000..53aca8b3 --- /dev/null +++ b/Unity.Transforms/LocalPositionComponent.cs @@ -0,0 +1,32 @@ +using System; +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms +{ + /// + /// User specified position in local space. + /// 1. If a TransformParent exists, the object to world matrix will be determined + /// by the parent's transform. Otherwise, it will be a Unit matrix. + /// 2. If a TransformMatrix exists, the calculated world position will be stored + /// as the translation in that matrix. + /// 3. If a Position exists, the calculated world position will be stored in that + /// component data. + /// 4. If TransformParent refers to the entity this component is associated with, + /// the calculated world position will be used as the translation in the object + /// to world matrix for the entity associated with this component, regardless of + /// if it is stored in a TransformMatrix. + /// + [Serializable] + public struct LocalPosition : IComponentData + { + public float3 Value; + + public LocalPosition(float3 position) + { + Value = position; + } + } + + public class LocalPositionComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms/LocalPositionComponent.cs.meta b/Unity.Transforms/LocalPositionComponent.cs.meta new file mode 100644 index 00000000..f3072d18 --- /dev/null +++ b/Unity.Transforms/LocalPositionComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e13a4d4e64324cc4b24fbea71211b5e0 +timeCreated: 1517958141 \ No newline at end of file diff --git a/Unity.Transforms/LocalRotationComponent.cs b/Unity.Transforms/LocalRotationComponent.cs new file mode 100644 index 00000000..65a3d20b --- /dev/null +++ b/Unity.Transforms/LocalRotationComponent.cs @@ -0,0 +1,32 @@ +using System; +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms +{ + /// + /// User specified rotation in local space. + /// 1. If a TransformParent exists, the object to world matrix will be determined + /// by the parent's transform. Otherwise, it will be a Unit matrix. + /// 2. If a TransformMatrix exists, the calculated world rortation will be stored + /// as the rotation part of that matrix. + /// 3. If a Rotation exists, the calculated world rotation will be stored in that + /// component data. + /// 4. If TransformParent refers to the entity this component is associated with, + /// the calculated world rotation will be used as the rotation part in the object + /// to world matrix for the entity associated with this component, regardless of + /// if it is stored in a TransformMatrix. + /// + [Serializable] + public struct LocalRotation : IComponentData + { + public quaternion Value; + + public LocalRotation(quaternion rotation) + { + Value = rotation; + } + } + + public class LocalRotationComponent : ComponentDataWrapper { } +} \ No newline at end of file diff --git a/Unity.Transforms/LocalRotationComponent.cs.meta b/Unity.Transforms/LocalRotationComponent.cs.meta new file mode 100644 index 00000000..8eab4345 --- /dev/null +++ b/Unity.Transforms/LocalRotationComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3ef0b3e1748b45ad9e8f5ff968a58de4 +timeCreated: 1517967416 \ No newline at end of file diff --git a/Unity.Transforms/MoveForwardComponent.cs b/Unity.Transforms/MoveForwardComponent.cs new file mode 100644 index 00000000..8c3f3744 --- /dev/null +++ b/Unity.Transforms/MoveForwardComponent.cs @@ -0,0 +1,8 @@ +using Unity.Entities; + +namespace Unity.Transforms +{ + public struct MoveForward : ISharedComponentData { } + + public class MoveForwardComponent : SharedComponentDataWrapper { } +} diff --git a/Unity.Transforms/MoveForwardComponent.cs.meta b/Unity.Transforms/MoveForwardComponent.cs.meta new file mode 100644 index 00000000..4d75c846 --- /dev/null +++ b/Unity.Transforms/MoveForwardComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 86ca4290c6b3473e913676ba4860db32 +timeCreated: 1517425182 \ No newline at end of file diff --git a/Unity.Transforms/MoveForwardSystem.cs b/Unity.Transforms/MoveForwardSystem.cs new file mode 100644 index 00000000..c3dd9719 --- /dev/null +++ b/Unity.Transforms/MoveForwardSystem.cs @@ -0,0 +1,91 @@ +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Mathematics.Experimental; +using Unity.Transforms; +using UnityEngine; + +namespace Unity.Transforms +{ + [UpdateAfter(typeof(TransformInputBarrier))] + [UpdateBefore(typeof(TransformSystem))] + public class MoveForwardSystem : JobComponentSystem + { + [ComputeJobOptimization] + struct MoveForwardRotation : IJobParallelFor + { + public ComponentDataArray positions; + [ReadOnly] public ComponentDataArray rotations; + [ReadOnly] public ComponentDataArray moveSpeeds; + public float dt; + + public void Execute(int i) + { + positions[i] = new Position + { + Value = positions[i].Value + (dt * moveSpeeds[i].speed * math.forward(rotations[i].Value)) + }; + } + } + + [ComputeJobOptimization] + struct MoveForwardHeading : IJobParallelFor + { + public ComponentDataArray positions; + [ReadOnly] public ComponentDataArray headings; + [ReadOnly] public ComponentDataArray moveSpeeds; + public float dt; + + public void Execute(int i) + { + positions[i] = new Position + { + Value = positions[i].Value + (dt * moveSpeeds[i].speed * math_experimental.normalizeSafe(headings[i].Value)) + }; + } + } + + ComponentGroup m_MoveForwardRotationGroup; + ComponentGroup m_MoveForwardHeadingGroup; + + protected override void OnCreateManager(int capacity) + { + m_MoveForwardRotationGroup = GetComponentGroup( + ComponentType.ReadOnly(typeof(MoveForward)), + ComponentType.ReadOnly(typeof(Rotation)), + ComponentType.ReadOnly(typeof(MoveSpeed)), + typeof(Position)); + + m_MoveForwardHeadingGroup = GetComponentGroup( + ComponentType.ReadOnly(typeof(MoveForward)), + ComponentType.Subtractive(typeof(Rotation)), + ComponentType.ReadOnly(typeof(Heading)), + ComponentType.ReadOnly(typeof(MoveSpeed)), + typeof(Position)); + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + var moveForwardRotationJob = new MoveForwardRotation + { + positions = m_MoveForwardRotationGroup.GetComponentDataArray(), + rotations = m_MoveForwardRotationGroup.GetComponentDataArray(), + moveSpeeds = m_MoveForwardRotationGroup.GetComponentDataArray(), + dt = Time.deltaTime + }; + var moveForwardRotationJobHandle = moveForwardRotationJob.Schedule(m_MoveForwardRotationGroup.CalculateLength(), 64, inputDeps); + + var moveForwardHeadingJob = new MoveForwardHeading + { + positions = m_MoveForwardHeadingGroup.GetComponentDataArray(), + headings = m_MoveForwardHeadingGroup.GetComponentDataArray(), + moveSpeeds = m_MoveForwardHeadingGroup.GetComponentDataArray(), + dt = Time.deltaTime + }; + var moveForwardHeadingJobHandle = moveForwardHeadingJob.Schedule(m_MoveForwardHeadingGroup.CalculateLength(), 64, inputDeps); + + return JobHandle.CombineDependencies(moveForwardHeadingJobHandle,moveForwardRotationJobHandle); + } + } +} diff --git a/Unity.Transforms/MoveForwardSystem.cs.meta b/Unity.Transforms/MoveForwardSystem.cs.meta new file mode 100644 index 00000000..a33fab70 --- /dev/null +++ b/Unity.Transforms/MoveForwardSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1dbc1c6ecf874e42ba0e1bc80d0e547f +timeCreated: 1517425225 \ No newline at end of file diff --git a/Unity.Transforms/MoveSpeedComponent.cs b/Unity.Transforms/MoveSpeedComponent.cs new file mode 100644 index 00000000..a8421fa8 --- /dev/null +++ b/Unity.Transforms/MoveSpeedComponent.cs @@ -0,0 +1,17 @@ +using System; +using Unity.Entities; + +namespace Unity.Transforms +{ + /// + /// Store float speed. This component requests that if another component is moving the PositionComponent + /// it should respect this value and move the position at the constant speed specified. + /// + [Serializable] + public struct MoveSpeed : IComponentData + { + public float speed; + } + + public class MoveSpeedComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms/MoveSpeedComponent.cs.meta b/Unity.Transforms/MoveSpeedComponent.cs.meta new file mode 100644 index 00000000..d1af004c --- /dev/null +++ b/Unity.Transforms/MoveSpeedComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6c2eb2f15b9d4252b7cd31a6a06645cb +timeCreated: 1517352301 \ No newline at end of file diff --git a/Unity.Transforms/PositionComponent.cs b/Unity.Transforms/PositionComponent.cs new file mode 100644 index 00000000..4306a9fe --- /dev/null +++ b/Unity.Transforms/PositionComponent.cs @@ -0,0 +1,32 @@ +using System; +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms +{ + /// + /// User specified (or calculated) World position + /// 1. If a TransformParent exists and no LocalPosition exists, the value + /// will be used as the object to World translation irrespective of the + /// parent object to World matrix. + /// 2. If a TransformParent exists and a LocalPosition exists, the calculated + /// World position will be stored in this value by the TransformSystem. + /// 3. If a TransformMatrix exists, the value will be stored as the translation + /// part of the matrix. + /// + [Serializable] + public struct Position : IComponentData + { + public float3 Value; + + public Position(float3 position) + { + Value = position; + } + } +} + +namespace Unity.Transforms +{ + public class PositionComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms/PositionComponent.cs.meta b/Unity.Transforms/PositionComponent.cs.meta new file mode 100644 index 00000000..37a2b12b --- /dev/null +++ b/Unity.Transforms/PositionComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 168b0e3756704251859c57ee75912ca1 +timeCreated: 1516900871 \ No newline at end of file diff --git a/Unity.Transforms/RotationComponent.cs b/Unity.Transforms/RotationComponent.cs new file mode 100644 index 00000000..d1ff8eb8 --- /dev/null +++ b/Unity.Transforms/RotationComponent.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms +{ + /// + /// User specified (or calculated) world rotation + /// 1. If a TransformParent exists and no LocalRotation exists, the value + /// will be used as the object to world translation irrespective of the + /// parent object to world matrix. + /// 2. If a TransformParent exists and a LocalRotation exists, the calculated + /// world rotation will be stored in this value by the TransformSystem. + /// 3. If a TrasformMatrix exists, the value will be stored as the rotation + /// part of the matrix. + /// + public struct Rotation : IComponentData + { + public quaternion Value; + + public Rotation(quaternion rotation) + { + Value = rotation; + } + } + + public class RotationComponent : ComponentDataWrapper { } +} \ No newline at end of file diff --git a/Unity.Transforms/RotationComponent.cs.meta b/Unity.Transforms/RotationComponent.cs.meta new file mode 100644 index 00000000..c4b77458 --- /dev/null +++ b/Unity.Transforms/RotationComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2327f3002fc94f2b91d287c740439550 +timeCreated: 1517270232 \ No newline at end of file diff --git a/Unity.Transforms/TransformInputBarrier.cs b/Unity.Transforms/TransformInputBarrier.cs new file mode 100644 index 00000000..561db699 --- /dev/null +++ b/Unity.Transforms/TransformInputBarrier.cs @@ -0,0 +1,9 @@ +using Unity.Entities; + +namespace Unity.Transforms +{ + public class TransformInputBarrier : BarrierSystem + { + + } +} diff --git a/Unity.Transforms/TransformInputBarrier.cs.meta b/Unity.Transforms/TransformInputBarrier.cs.meta new file mode 100644 index 00000000..c93dc45d --- /dev/null +++ b/Unity.Transforms/TransformInputBarrier.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 06f01c523685c485db9051993a0fdb97 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms/TransformMatrixComponent.cs b/Unity.Transforms/TransformMatrixComponent.cs new file mode 100644 index 00000000..70933e34 --- /dev/null +++ b/Unity.Transforms/TransformMatrixComponent.cs @@ -0,0 +1,16 @@ +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms +{ + /// + /// Store a (calculated) float4x4 matrix representing the Object to World position/rotation/scale transformation. + /// Required by other systems. e.g. MeshInstanceRenderer + /// + public struct TransformMatrix : IComponentData + { + public float4x4 Value; + } + + public class TransformMatrixComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms/TransformMatrixComponent.cs.meta b/Unity.Transforms/TransformMatrixComponent.cs.meta new file mode 100644 index 00000000..8f00b62c --- /dev/null +++ b/Unity.Transforms/TransformMatrixComponent.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2caacf465eeae43f39a6a13e442d1dc0 +timeCreated: 1497278756 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 100 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms/TransformParentComponent.cs b/Unity.Transforms/TransformParentComponent.cs new file mode 100644 index 00000000..583ef903 --- /dev/null +++ b/Unity.Transforms/TransformParentComponent.cs @@ -0,0 +1,23 @@ +using Unity.Entities; + +namespace Unity.Transforms +{ + /// + /// User assigned parent Entity. + /// Local transformations (e.g. LocalPosition, LocalRotation) will be relative + /// to the object to world matrix associated with the parent entity. + /// The parent's object to world matrix may only be temporary if it's not explicitly + /// stored in a TransformMatrix. + /// + public struct TransformParent : IComponentData + { + public Entity Value; + + public TransformParent(Entity parent) + { + Value = parent; + } + } + + public class TransformParentComponent : ComponentDataWrapper { } +} \ No newline at end of file diff --git a/Unity.Transforms/TransformParentComponent.cs.meta b/Unity.Transforms/TransformParentComponent.cs.meta new file mode 100644 index 00000000..760330db --- /dev/null +++ b/Unity.Transforms/TransformParentComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 18fdf4d20b7547b58faaa473d24d9206 +timeCreated: 1517957251 \ No newline at end of file diff --git a/Unity.Transforms/TransformSystem.cs b/Unity.Transforms/TransformSystem.cs new file mode 100644 index 00000000..967d16b9 --- /dev/null +++ b/Unity.Transforms/TransformSystem.cs @@ -0,0 +1,910 @@ +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Unity.Transforms +{ + [UnityEngine.ExecuteInEditMode] + public class TransformSystem : JobComponentSystem + { + [Inject] [ReadOnly] ComponentDataFromEntity m_LocalPositions; + [Inject] [ReadOnly] ComponentDataFromEntity m_LocalRotations; + [Inject] ComponentDataFromEntity m_Positions; + [Inject] ComponentDataFromEntity m_Rotations; + [Inject] ComponentDataFromEntity m_TransformMatrices; + + // +Rotation +Position -Heading -TransformMatrix + struct RootRotTransNoTransformGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public ComponentDataArray rotations; + [ReadOnly] public SubtractiveComponent parents; + [ReadOnly] public SubtractiveComponent headings; + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public EntityArray entities; + [ReadOnly] public SubtractiveComponent transforms; + public int Length; + } + [Inject] RootRotTransNoTransformGroup m_RootRotTransNoTransformGroup; + + // +Rotation +Position -Heading +TransformMatrix + struct RootRotTransTransformGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public ComponentDataArray rotations; + [ReadOnly] public SubtractiveComponent parents; + [ReadOnly] public SubtractiveComponent headings; + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public EntityArray entities; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + public int Length; + } + [Inject] RootRotTransTransformGroup m_RootRotTransTransformGroup; + + // +Rotation -Position -Heading -TransformMatrix + struct RootRotNoTransformGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public ComponentDataArray rotations; + [ReadOnly] public SubtractiveComponent parents; + [ReadOnly] public SubtractiveComponent headings; + [ReadOnly] public SubtractiveComponent positions; + [ReadOnly] public EntityArray entities; + [ReadOnly] public SubtractiveComponent transforms; + public int Length; + } + [Inject] RootRotNoTransformGroup m_RootRotNoTransformGroup; + + // +Rotation -Position -Heading +TransformMatrix + struct RootRotTransformGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public ComponentDataArray rotations; + [ReadOnly] public SubtractiveComponent parents; + [ReadOnly] public SubtractiveComponent headings; + [ReadOnly] public SubtractiveComponent positions; + [ReadOnly] public EntityArray entities; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + public int Length; + } + [Inject] RootRotTransformGroup m_RootRotTransformGroup; + + // -Rotation +Position -Heading -TransformMatrix + struct RootTransNoTransformGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public SubtractiveComponent rotations; + [ReadOnly] public SubtractiveComponent parents; + [ReadOnly] public SubtractiveComponent headings; + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public EntityArray entities; + [ReadOnly] public SubtractiveComponent transforms; + public int Length; + } + [Inject] RootTransNoTransformGroup m_RootTransNoTransformGroup; + + // -Rotation +Position -Heading +TransformMatrix + struct RootTransTransformGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public SubtractiveComponent rotations; + [ReadOnly] public SubtractiveComponent parents; + [ReadOnly] public SubtractiveComponent headings; + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public EntityArray entities; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + public int Length; + } + [Inject] RootTransTransformGroup m_RootTransTransformGroup; + + // -Rotation +Position +Heading +TransformMatrix + struct RootHeadingTransTransformGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public SubtractiveComponent rotations; + [ReadOnly] public SubtractiveComponent parents; + [ReadOnly] public ComponentDataArray headings; + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public EntityArray entities; + // @todo Why doesn't this throw exception? + // [ReadOnly] public ComponentDataArray transforms; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + public int Length; + } + [Inject] RootHeadingTransTransformGroup m_RootHeadingTransTransformGroup; + + // -Rotation +Position +Heading -TransformMatrix + struct RootHeadingTransNoTransformGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public SubtractiveComponent rotations; + [ReadOnly] public SubtractiveComponent parents; + [ReadOnly] public ComponentDataArray headings; + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public EntityArray entities; + [ReadOnly] public SubtractiveComponent transforms; + public int Length; + } + [Inject] RootHeadingTransNoTransformGroup m_RootHeadingTransNoTransformGroup; + + struct ParentGroup + { + [ReadOnly] public SubtractiveComponent> transfromExternal; + [ReadOnly] public ComponentDataArray transformParents; + [ReadOnly] public EntityArray entities; + public int Length; + } + [Inject] ParentGroup m_ParentGroup; + + [ComputeJobOptimization] + struct UpdateRotTransTransformRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray rotations; + [ReadOnly] public ComponentDataArray positions; + public NativeArray matrices; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + + public void Execute(int index) + { + float4x4 matrix = math.rottrans(rotations[index].Value, positions[index].Value); + matrices[index] = matrix; + transforms[index] = new TransformMatrix {Value = matrix}; + } + } + + [ComputeJobOptimization] + struct UpdateRotTransTransformNoHierarchyRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray rotations; + [ReadOnly] public ComponentDataArray positions; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + + public void Execute(int index) + { + float4x4 matrix = math.rottrans(rotations[index].Value, positions[index].Value); + transforms[index] = new TransformMatrix {Value = matrix}; + } + } + + [ComputeJobOptimization] + struct UpdateRotTransNoTransformRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray rotations; + [ReadOnly] public ComponentDataArray positions; + public NativeArray matrices; + + public void Execute(int index) + { + float4x4 matrix = math.rottrans(rotations[index].Value, positions[index].Value); + matrices[index] = matrix; + } + } + + [ComputeJobOptimization] + struct UpdateRotTransformRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray rotations; + public NativeArray matrices; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + + public void Execute(int index) + { + float4x4 matrix = math.rottrans(rotations[index].Value, new float3()); + matrices[index] = matrix; + transforms[index] = new TransformMatrix {Value = matrix}; + } + } + + [ComputeJobOptimization] + struct UpdateRotTransformNoHierarchyRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray rotations; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + + public void Execute(int index) + { + float4x4 matrix = math.rottrans(rotations[index].Value, new float3()); + transforms[index] = new TransformMatrix {Value = matrix}; + } + } + + [ComputeJobOptimization] + struct UpdateRotNoTransformRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray rotations; + public NativeArray matrices; + + public void Execute(int index) + { + float4x4 matrix = math.rottrans(rotations[index].Value, new float3()); + matrices[index] = matrix; + } + } + + [ComputeJobOptimization] + struct UpdateTransTransformRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray positions; + public NativeArray matrices; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + + public void Execute(int index) + { + float4x4 matrix = math.translate(positions[index].Value); + matrices[index] = matrix; + transforms[index] = new TransformMatrix {Value = matrix}; + } + } + + [ComputeJobOptimization] + struct UpdateTransTransformNoHierarchyRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray positions; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + + public void Execute(int index) + { + float4x4 matrix = math.translate(positions[index].Value); + transforms[index] = new TransformMatrix {Value = matrix}; + } + } + + [ComputeJobOptimization] + struct UpdateTransNoTransformRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray positions; + public NativeArray matrices; + + public void Execute(int index) + { + float4x4 matrix = math.translate(positions[index].Value); + matrices[index] = matrix; + } + } + + [ComputeJobOptimization] + struct UpdateHeadingTransTransformRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public ComponentDataArray headings; + public NativeArray matrices; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + + public void Execute(int index) + { + var matrix = math.lookRotationToMatrix(positions[index].Value, headings[index].Value, math.up()); + matrices[index] = matrix; + transforms[index] = new TransformMatrix {Value = matrix}; + } + } + + [ComputeJobOptimization] + struct UpdateHeadingTransTransformNoHierarchyRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public ComponentDataArray headings; + [NativeDisableContainerSafetyRestriction] + public ComponentDataArray transforms; + + public void Execute(int index) + { + var matrix = math.lookRotationToMatrix(positions[index].Value, headings[index].Value, math.up()); + transforms[index] = new TransformMatrix {Value = matrix}; + } + } + + [ComputeJobOptimization] + struct UpdateHeadingTransNoTransformRoots : IJobParallelFor + { + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public ComponentDataArray headings; + public NativeArray matrices; + + public void Execute(int index) + { + var matrix = math.lookRotationToMatrix(positions[index].Value, headings[index].Value, math.up()); + matrices[index] = matrix; + } + } + + [ComputeJobOptimization] + struct BuildHierarchy : IJobParallelFor + { + public NativeMultiHashMap.Concurrent hierarchy; + [ReadOnly] public ComponentDataArray transformParents; + [ReadOnly] public EntityArray entities; + + public void Execute(int index) + { + hierarchy.Add(transformParents[index].Value,entities[index]); + } + } + + [ComputeJobOptimization] + struct UpdateSubHierarchy : IJobParallelFor + { + [ReadOnly] public NativeMultiHashMap hierarchy; + [DeallocateOnJobCompletion] [ReadOnly] public NativeArray roots; + [DeallocateOnJobCompletion] [ReadOnly] public NativeArray rootMatrices; + + [ReadOnly] public ComponentDataFromEntity localPositions; + [ReadOnly] public ComponentDataFromEntity localRotations; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity positions; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity rotations; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity transformMatrices; + + void TransformTree(Entity entity,float4x4 parentMatrix) + { + var position = new float3(); + var rotation = quaternion.identity; + + if (positions.Exists(entity)) + { + position = positions[entity].Value; + } + + if (rotations.Exists(entity)) + { + rotation = rotations[entity].Value; + } + + if (localPositions.Exists(entity)) + { + var worldPosition = math.mul(parentMatrix,new float4(localPositions[entity].Value,1.0f)); + position = new float3(worldPosition.x,worldPosition.y,worldPosition.z); + if (positions.Exists(entity)) + { + positions[entity] = new Position {Value = position}; + } + } + + if (localRotations.Exists(entity)) + { + var parentRotation = math.matrixToQuat(parentMatrix.m0.xyz, parentMatrix.m1.xyz, parentMatrix.m2.xyz); + var localRotation = localRotations[entity].Value; + rotation = math.mul(parentRotation, localRotation); + if (rotations.Exists(entity)) + { + rotations[entity] = new Rotation { Value = rotation }; + } + } + + float4x4 matrix = math.rottrans(rotation, position); + if (transformMatrices.Exists(entity)) + { + transformMatrices[entity] = new TransformMatrix {Value = matrix}; + } + + Entity child; + NativeMultiHashMapIterator iterator; + bool found = hierarchy.TryGetFirstValue(entity, out child, out iterator); + while (found) + { + TransformTree(child,matrix); + found = hierarchy.TryGetNextValue(out child, ref iterator); + } + } + + public void Execute(int i) + { + Entity entity = roots[i]; + float4x4 matrix = rootMatrices[i]; + Entity child; + NativeMultiHashMapIterator iterator; + bool found = hierarchy.TryGetFirstValue(entity, out child, out iterator); + while (found) + { + TransformTree(child,matrix); + found = hierarchy.TryGetNextValue(out child, ref iterator); + } + } + } + + [ComputeJobOptimization] + struct ClearHierarchy : IJob + { + public NativeMultiHashMap hierarchy; + + public void Execute() + { + hierarchy.Clear(); + } + } + + NativeMultiHashMap m_Hierarchy; + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + int rootCount = m_RootRotTransTransformGroup.Length + m_RootRotTransNoTransformGroup.Length + + m_RootRotTransformGroup.Length + m_RootRotNoTransformGroup.Length + + m_RootTransTransformGroup.Length + m_RootTransNoTransformGroup.Length + + m_RootHeadingTransTransformGroup.Length + m_RootHeadingTransNoTransformGroup.Length; + if (rootCount == 0) + { + return inputDeps; + } + + var updateRootsDeps = inputDeps; + JobHandle? updateRootsBarrierJobHandle = null; + + // + // Update Roots (No Hierachies) + // + + if (m_ParentGroup.Length == 0) + { + if (m_RootRotTransTransformGroup.Length > 0) + { + var updateRotTransTransformRootsJob = new UpdateRotTransTransformNoHierarchyRoots + { + rotations = m_RootRotTransTransformGroup.rotations, + positions = m_RootRotTransTransformGroup.positions, + transforms = m_RootRotTransTransformGroup.transforms + }; + var updateRotTransTransformRootsJobHandle = updateRotTransTransformRootsJob.Schedule(m_RootRotTransTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = (updateRootsBarrierJobHandle == null)?updateRotTransTransformRootsJobHandle: JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateRotTransTransformRootsJobHandle); + } + + if (m_RootRotTransformGroup.Length > 0) + { + var updateRotTransformRootsJob = new UpdateRotTransformNoHierarchyRoots + { + rotations = m_RootRotTransformGroup.rotations, + transforms = m_RootRotTransformGroup.transforms + }; + var updateRotTransformRootsJobHandle = updateRotTransformRootsJob.Schedule(m_RootRotTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = (updateRootsBarrierJobHandle == null)?updateRotTransformRootsJobHandle: JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateRotTransformRootsJobHandle); + } + + if (m_RootTransTransformGroup.Length > 0) + { + var updateTransTransformRootsJob = new UpdateTransTransformNoHierarchyRoots + { + positions = m_RootTransTransformGroup.positions, + transforms = m_RootTransTransformGroup.transforms + }; + var updateTransTransformRootsJobHandle = updateTransTransformRootsJob.Schedule(m_RootTransTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = (updateRootsBarrierJobHandle == null)?updateTransTransformRootsJobHandle: JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateTransTransformRootsJobHandle); + } + + if (m_RootHeadingTransTransformGroup.Length > 0) + { + var updateHeadingTransTransformRootsJob = new UpdateHeadingTransTransformNoHierarchyRoots + { + headings = m_RootHeadingTransTransformGroup.headings, + positions = m_RootHeadingTransTransformGroup.positions, + transforms = m_RootHeadingTransTransformGroup.transforms + }; + var updateHeadingTransTransformRootsJobHandle = updateHeadingTransTransformRootsJob.Schedule(m_RootHeadingTransTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = (updateRootsBarrierJobHandle == null)?updateHeadingTransTransformRootsJobHandle: JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateHeadingTransTransformRootsJobHandle); + } + + return (updateRootsBarrierJobHandle == null) ? updateRootsDeps : updateRootsBarrierJobHandle.Value; + } + + // + // Update Roots (Hierarchies exist) + // + + if (m_ParentGroup.Length > 0) + { + m_Hierarchy.Capacity = math.max(m_ParentGroup.Length + rootCount,m_Hierarchy.Capacity); + + var clearHierarchyJob = new ClearHierarchy + { + hierarchy = m_Hierarchy + }; + var clearHierarchyJobHandle = clearHierarchyJob.Schedule(updateRootsDeps); + + var buildHierarchyJob = new BuildHierarchy + { + hierarchy = m_Hierarchy, + transformParents = m_ParentGroup.transformParents, + entities = m_ParentGroup.entities + }; + var buildHierarchyJobHandle = buildHierarchyJob.Schedule(m_ParentGroup.Length, 64, clearHierarchyJobHandle); + updateRootsBarrierJobHandle = buildHierarchyJobHandle; + } + + NativeArray? rotTransTransformRootMatrices = null; + if (m_RootRotTransTransformGroup.Length > 0) + { + rotTransTransformRootMatrices = new NativeArray(m_RootRotTransTransformGroup.Length, Allocator.TempJob,NativeArrayOptions.UninitializedMemory); + var updateRotTransTransformRootsJob = new UpdateRotTransTransformRoots + { + rotations = m_RootRotTransTransformGroup.rotations, + positions = m_RootRotTransTransformGroup.positions, + matrices = rotTransTransformRootMatrices.Value, + transforms = m_RootRotTransTransformGroup.transforms + }; + var updateRotTransTransformRootsJobHandle = updateRotTransTransformRootsJob.Schedule(m_RootRotTransTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateRotTransTransformRootsJobHandle); + } + + NativeArray? rotTransNoTransformRootMatrices = null; + if (m_RootRotTransNoTransformGroup.Length > 0) + { + rotTransNoTransformRootMatrices = new NativeArray(m_RootRotTransNoTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var updateRotTransNoTransformRootsJob = new UpdateRotTransNoTransformRoots + { + rotations = m_RootRotTransNoTransformGroup.rotations, + positions = m_RootRotTransNoTransformGroup.positions, + matrices = rotTransNoTransformRootMatrices.Value + }; + var updateRotTransNoTransformRootsJobHandle = updateRotTransNoTransformRootsJob.Schedule(m_RootRotTransNoTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateRotTransNoTransformRootsJobHandle); + } + + NativeArray? rotTransformRootMatrices = null; + if (m_RootRotTransformGroup.Length > 0) + { + rotTransformRootMatrices = new NativeArray(m_RootRotTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var updateRotTransformRootsJob = new UpdateRotTransformRoots + { + rotations = m_RootRotTransformGroup.rotations, + matrices = rotTransformRootMatrices.Value, + transforms = m_RootRotTransformGroup.transforms + }; + var updateRotTransformRootsJobHandle = updateRotTransformRootsJob.Schedule(m_RootRotTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateRotTransformRootsJobHandle); + } + + NativeArray? rotNoTransformRootMatrices = null; + if (m_RootRotNoTransformGroup.Length > 0) + { + rotNoTransformRootMatrices = new NativeArray(m_RootRotNoTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var updateRotNoTransformRootsJob = new UpdateRotNoTransformRoots + { + rotations = m_RootRotNoTransformGroup.rotations, + matrices = rotNoTransformRootMatrices.Value + }; + var updateRotNoTransformRootsJobHandle = updateRotNoTransformRootsJob.Schedule(m_RootRotNoTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateRotNoTransformRootsJobHandle); + } + + NativeArray? transTransformRootMatrices = null; + if (m_RootTransTransformGroup.Length > 0) + { + transTransformRootMatrices = new NativeArray(m_RootTransTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var updateTransTransformRootsJob = new UpdateTransTransformRoots + { + positions = m_RootTransTransformGroup.positions, + matrices = transTransformRootMatrices.Value, + transforms = m_RootTransTransformGroup.transforms + }; + var updateTransTransformRootsJobHandle = updateTransTransformRootsJob.Schedule(m_RootTransTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateTransTransformRootsJobHandle); + } + + NativeArray? transNoTransformRootMatrices = null; + if (m_RootTransNoTransformGroup.Length > 0) + { + transNoTransformRootMatrices = new NativeArray(m_RootTransNoTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var updateTransNoTransformRootsJob = new UpdateTransNoTransformRoots + { + positions = m_RootTransNoTransformGroup.positions, + matrices = transNoTransformRootMatrices.Value + }; + var updateTransNoTransformRootsJobHandle = updateTransNoTransformRootsJob.Schedule(m_RootTransNoTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateTransNoTransformRootsJobHandle); + } + + NativeArray? headingTransTransformRootMatrices = null; + if (m_RootHeadingTransTransformGroup.Length > 0) + { + headingTransTransformRootMatrices = new NativeArray(m_RootHeadingTransTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var updateHeadingTransTransformRootsJob = new UpdateHeadingTransTransformRoots + { + positions = m_RootHeadingTransTransformGroup.positions, + headings = m_RootHeadingTransTransformGroup.headings, + matrices = headingTransTransformRootMatrices.Value, + transforms= m_RootHeadingTransTransformGroup.transforms + }; + var updateHeadingTransTransformRootsJobHandle = updateHeadingTransTransformRootsJob.Schedule(m_RootHeadingTransTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateHeadingTransTransformRootsJobHandle); + } + + NativeArray? headingTransNoTransformRootMatrices = null; + if (m_RootHeadingTransNoTransformGroup.Length > 0) + { + headingTransNoTransformRootMatrices = new NativeArray(m_RootHeadingTransNoTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var updateHeadingTransNoTransformRootsJob = new UpdateHeadingTransNoTransformRoots + { + positions = m_RootHeadingTransNoTransformGroup.positions, + headings = m_RootHeadingTransTransformGroup.headings, + matrices = headingTransNoTransformRootMatrices.Value + }; + var updateHeadingTransNoTransformRootsJobHandle = updateHeadingTransNoTransformRootsJob.Schedule(m_RootHeadingTransNoTransformGroup.Length, 64, updateRootsDeps); + updateRootsBarrierJobHandle = JobHandle.CombineDependencies(updateRootsBarrierJobHandle.Value, updateHeadingTransNoTransformRootsJobHandle); + } + + // + // Copy Root Entities for Sub Hierarchy Transform + // + + var copyRootEntitiesDeps = updateRootsBarrierJobHandle.Value; + var copyRootEntitiesBarrierJobHandle = new JobHandle(); + + NativeArray? rotTransTransformRoots = null; + if (m_RootRotTransTransformGroup.Length > 0) + { + rotTransTransformRoots = new NativeArray(m_RootRotTransTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var copyRotTransTransformRootsJob = new CopyEntities + { + Source = m_RootRotTransTransformGroup.entities, + Results = rotTransTransformRoots.Value + }; + var copyRotTransTransformRootsJobHandle = copyRotTransTransformRootsJob.Schedule(m_RootRotTransTransformGroup.Length, 64, copyRootEntitiesDeps); + copyRootEntitiesBarrierJobHandle = JobHandle.CombineDependencies(copyRootEntitiesBarrierJobHandle,copyRotTransTransformRootsJobHandle); + } + + NativeArray? rotTransNoTransformRoots = null; + if (m_RootRotTransNoTransformGroup.Length > 0) + { + rotTransNoTransformRoots = new NativeArray(m_RootRotTransNoTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var copyRotTransNoTransformRootsJob = new CopyEntities + { + Source = m_RootRotTransNoTransformGroup.entities, + Results = rotTransNoTransformRoots.Value + }; + var copyRotTransNoTransformRootsJobHandle = copyRotTransNoTransformRootsJob.Schedule(m_RootRotTransNoTransformGroup.Length, 64, copyRootEntitiesDeps); + copyRootEntitiesBarrierJobHandle = JobHandle.CombineDependencies(copyRootEntitiesBarrierJobHandle, copyRotTransNoTransformRootsJobHandle); + } + + NativeArray? rotTransformRoots = null; + if (m_RootRotTransformGroup.Length > 0) + { + rotTransformRoots = new NativeArray(m_RootRotTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var copyRotTransformRootsJob = new CopyEntities + { + Source = m_RootRotTransformGroup.entities, + Results = rotTransformRoots.Value + }; + var copyRotTransformRootsJobHandle = copyRotTransformRootsJob.Schedule(m_RootRotTransformGroup.Length, 64, copyRootEntitiesDeps); + copyRootEntitiesBarrierJobHandle = JobHandle.CombineDependencies(copyRootEntitiesBarrierJobHandle,copyRotTransformRootsJobHandle); + } + + NativeArray? rotNoTransformRoots = null; + if (m_RootRotNoTransformGroup.Length > 0) + { + rotNoTransformRoots = new NativeArray(m_RootRotNoTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var copyRotNoTransformRootsJob = new CopyEntities + { + Source = m_RootRotNoTransformGroup.entities, + Results = rotNoTransformRoots.Value + }; + var copyRotNoTransformRootsJobHandle = copyRotNoTransformRootsJob.Schedule(m_RootRotNoTransformGroup.Length, 64, copyRootEntitiesDeps); + copyRootEntitiesBarrierJobHandle = JobHandle.CombineDependencies(copyRootEntitiesBarrierJobHandle, copyRotNoTransformRootsJobHandle); + } + + NativeArray? transTransformRoots = null; + if (m_RootTransTransformGroup.Length > 0) + { + transTransformRoots = new NativeArray(m_RootTransTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var copyTransTransformRootsJob = new CopyEntities + { + Source = m_RootTransTransformGroup.entities, + Results = transTransformRoots.Value + }; + var copyTransTransformRootsJobHandle = copyTransTransformRootsJob.Schedule(m_RootTransTransformGroup.Length, 64, copyRootEntitiesDeps); + copyRootEntitiesBarrierJobHandle = JobHandle.CombineDependencies(copyRootEntitiesBarrierJobHandle,copyTransTransformRootsJobHandle); + } + + NativeArray? transNoTransformRoots = null; + if (m_RootTransNoTransformGroup.Length > 0) + { + transNoTransformRoots = new NativeArray(m_RootTransNoTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var copyTransNoTransformRootsJob = new CopyEntities + { + Source = m_RootTransNoTransformGroup.entities, + Results = transNoTransformRoots.Value + }; + var copyTransNoTransformRootsJobHandle = copyTransNoTransformRootsJob.Schedule(m_RootTransNoTransformGroup.Length, 64, copyRootEntitiesDeps); + copyRootEntitiesBarrierJobHandle = JobHandle.CombineDependencies(copyRootEntitiesBarrierJobHandle, copyTransNoTransformRootsJobHandle); + } + + NativeArray? headingTransTransformRoots = null; + if (m_RootHeadingTransTransformGroup.Length > 0) + { + headingTransTransformRoots = new NativeArray(m_RootHeadingTransTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var copyHeadingTransTransformRootsJob = new CopyEntities + { + Source = m_RootHeadingTransTransformGroup.entities, + Results = headingTransTransformRoots.Value + }; + var copyHeadingTransTransformRootsJobHandle = copyHeadingTransTransformRootsJob.Schedule(m_RootHeadingTransTransformGroup.Length, 64, copyRootEntitiesDeps); + copyRootEntitiesBarrierJobHandle = JobHandle.CombineDependencies(copyRootEntitiesBarrierJobHandle,copyHeadingTransTransformRootsJobHandle); + } + + NativeArray? headingTransNoTransformRoots = null; + if (m_RootHeadingTransNoTransformGroup.Length > 0) + { + headingTransNoTransformRoots = new NativeArray(m_RootHeadingTransNoTransformGroup.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var copyHeadingTransNoTransformRootsJob = new CopyEntities + { + Source = m_RootHeadingTransNoTransformGroup.entities, + Results = headingTransNoTransformRoots.Value + }; + var copyHeadingTransNoTransformRootsJobHandle = copyHeadingTransNoTransformRootsJob.Schedule(m_RootHeadingTransNoTransformGroup.Length, 64, copyRootEntitiesDeps); + copyRootEntitiesBarrierJobHandle = JobHandle.CombineDependencies(copyRootEntitiesBarrierJobHandle, copyHeadingTransNoTransformRootsJobHandle); + } + + // + // Update Sub Hierarchy + // + + var updateSubHierarchyDeps = copyRootEntitiesBarrierJobHandle; + var updateSubHierarchyBarrierJobHandle = new JobHandle(); + + if (m_RootRotTransTransformGroup.Length > 0) + { + var updateRotTransTransformHierarchyJob = new UpdateSubHierarchy + { + hierarchy = m_Hierarchy, + roots = rotTransTransformRoots.Value, + rootMatrices = rotTransTransformRootMatrices.Value, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + positions = m_Positions, + rotations = m_Rotations, + transformMatrices = m_TransformMatrices + }; + var updateRotTransTransformHierarchyJobHandle = updateRotTransTransformHierarchyJob.Schedule(rotTransTransformRoots.Value.Length,64,updateSubHierarchyDeps); + updateSubHierarchyDeps = updateRotTransTransformHierarchyJobHandle; + updateSubHierarchyBarrierJobHandle = JobHandle.CombineDependencies(updateSubHierarchyBarrierJobHandle,updateRotTransTransformHierarchyJobHandle); + } + + if (m_RootRotTransNoTransformGroup.Length > 0) + { + var updateRotTransNoTransformHierarchyJob = new UpdateSubHierarchy + { + hierarchy = m_Hierarchy, + roots = rotTransNoTransformRoots.Value, + rootMatrices = rotTransNoTransformRootMatrices.Value, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + positions = m_Positions, + rotations = m_Rotations, + transformMatrices = m_TransformMatrices + }; + var updateRotTransNoTransformHierarchyJobHandle = updateRotTransNoTransformHierarchyJob.Schedule(rotTransNoTransformRoots.Value.Length,64,updateSubHierarchyDeps); + updateSubHierarchyDeps = updateRotTransNoTransformHierarchyJobHandle; + updateSubHierarchyBarrierJobHandle = JobHandle.CombineDependencies(updateSubHierarchyBarrierJobHandle,updateRotTransNoTransformHierarchyJobHandle); + } + + if (m_RootRotTransformGroup.Length > 0) + { + var updateRotTransformHierarchyJob = new UpdateSubHierarchy + { + hierarchy = m_Hierarchy, + roots = rotTransformRoots.Value, + rootMatrices = rotTransformRootMatrices.Value, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + positions = m_Positions, + rotations = m_Rotations, + transformMatrices = m_TransformMatrices + }; + var updateRotTransformHierarchyJobHandle = updateRotTransformHierarchyJob.Schedule(rotTransformRoots.Value.Length,1,updateSubHierarchyDeps); + updateSubHierarchyDeps = updateRotTransformHierarchyJobHandle; + updateSubHierarchyBarrierJobHandle = JobHandle.CombineDependencies(updateSubHierarchyBarrierJobHandle,updateRotTransformHierarchyJobHandle); + } + + if (m_RootRotNoTransformGroup.Length > 0) + { + var updateRotNoTransformHierarchyJob = new UpdateSubHierarchy + { + hierarchy = m_Hierarchy, + roots = rotNoTransformRoots.Value, + rootMatrices = rotNoTransformRootMatrices.Value, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + positions = m_Positions, + rotations = m_Rotations, + transformMatrices = m_TransformMatrices + }; + var updateRotNoTransformHierarchyJobHandle = updateRotNoTransformHierarchyJob.Schedule(rotNoTransformRoots.Value.Length,1,updateSubHierarchyDeps); + updateSubHierarchyDeps = updateRotNoTransformHierarchyJobHandle; + updateSubHierarchyBarrierJobHandle = JobHandle.CombineDependencies(updateSubHierarchyBarrierJobHandle,updateRotNoTransformHierarchyJobHandle); + } + + if (m_RootTransTransformGroup.Length > 0) + { + var updateTransTransformHierarchyJob = new UpdateSubHierarchy + { + hierarchy = m_Hierarchy, + roots = transTransformRoots.Value, + rootMatrices = transTransformRootMatrices.Value, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + positions = m_Positions, + rotations = m_Rotations, + transformMatrices = m_TransformMatrices + }; + var updateTransTransformHierarchyJobHandle = updateTransTransformHierarchyJob.Schedule(transTransformRoots.Value.Length,1,updateSubHierarchyDeps); + updateSubHierarchyDeps = updateTransTransformHierarchyJobHandle; + updateSubHierarchyBarrierJobHandle = JobHandle.CombineDependencies(updateSubHierarchyBarrierJobHandle,updateTransTransformHierarchyJobHandle); + } + + if (m_RootTransNoTransformGroup.Length > 0) + { + var updateTransNoTransformHierarchyJob = new UpdateSubHierarchy + { + hierarchy = m_Hierarchy, + roots = transNoTransformRoots.Value, + rootMatrices = transNoTransformRootMatrices.Value, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + positions = m_Positions, + rotations = m_Rotations, + transformMatrices = m_TransformMatrices + }; + var updateTransNoTransformHierarchyJobHandle = updateTransNoTransformHierarchyJob.Schedule(transNoTransformRoots.Value.Length,1,updateSubHierarchyDeps); + updateSubHierarchyDeps = updateTransNoTransformHierarchyJobHandle; + updateSubHierarchyBarrierJobHandle = JobHandle.CombineDependencies(updateSubHierarchyBarrierJobHandle,updateTransNoTransformHierarchyJobHandle); + } + + if (m_RootHeadingTransTransformGroup.Length > 0) + { + var updateHeadingTransTransformHierarchyJob = new UpdateSubHierarchy + { + hierarchy = m_Hierarchy, + roots = headingTransTransformRoots.Value, + rootMatrices = headingTransTransformRootMatrices.Value, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + positions = m_Positions, + rotations = m_Rotations, + transformMatrices = m_TransformMatrices + }; + var updateHeadingTransTransformHierarchyJobHandle = updateHeadingTransTransformHierarchyJob.Schedule(headingTransTransformRoots.Value.Length,1,updateSubHierarchyDeps); + updateSubHierarchyDeps = updateHeadingTransTransformHierarchyJobHandle; + updateSubHierarchyBarrierJobHandle = JobHandle.CombineDependencies(updateSubHierarchyBarrierJobHandle,updateHeadingTransTransformHierarchyJobHandle); + } + + if (m_RootHeadingTransNoTransformGroup.Length > 0) + { + var updateHeadingTransNoTransformHierarchyJob = new UpdateSubHierarchy + { + hierarchy = m_Hierarchy, + roots = headingTransNoTransformRoots.Value, + rootMatrices = headingTransNoTransformRootMatrices.Value, + localPositions = m_LocalPositions, + localRotations = m_LocalRotations, + positions = m_Positions, + rotations = m_Rotations, + transformMatrices = m_TransformMatrices + }; + var updateHeadingTransNoTransformHierarchyJobHandle = updateHeadingTransNoTransformHierarchyJob.Schedule(headingTransNoTransformRoots.Value.Length,1,updateSubHierarchyDeps); + updateSubHierarchyDeps = updateHeadingTransNoTransformHierarchyJobHandle; + updateSubHierarchyBarrierJobHandle = JobHandle.CombineDependencies(updateSubHierarchyBarrierJobHandle,updateHeadingTransNoTransformHierarchyJobHandle); + } + + return updateSubHierarchyBarrierJobHandle; + } + + protected override void OnCreateManager(int capacity) + { + m_Hierarchy = new NativeMultiHashMap(capacity, Allocator.Persistent); + } + + protected override void OnDestroyManager() + { + m_Hierarchy.Dispose(); + } + + } +} diff --git a/Unity.Transforms/TransformSystem.cs.meta b/Unity.Transforms/TransformSystem.cs.meta new file mode 100644 index 00000000..8e45a971 --- /dev/null +++ b/Unity.Transforms/TransformSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 36052dfc84b84aacb9a693403cfb684c +timeCreated: 1516901492 \ No newline at end of file diff --git a/Unity.Transforms/Unity.Transforms.asmdef b/Unity.Transforms/Unity.Transforms.asmdef new file mode 100644 index 00000000..69f335b0 --- /dev/null +++ b/Unity.Transforms/Unity.Transforms.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Unity.Transforms", + "references": [ + "Unity.Entities", + "Unity.Entities.Hybrid", + "Unity.Collections", + "Unity.Burst", + "Unity.Jobs", + "Unity.Mathematics" + ], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [] +} diff --git a/Unity.Transforms/Unity.Transforms.asmdef.meta b/Unity.Transforms/Unity.Transforms.asmdef.meta new file mode 100644 index 00000000..2ae7f502 --- /dev/null +++ b/Unity.Transforms/Unity.Transforms.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d201e050170144f448ed155bdfbf78e8 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms2D.meta b/Unity.Transforms2D.meta new file mode 100644 index 00000000..5eb1410f --- /dev/null +++ b/Unity.Transforms2D.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e3b748cdcd934cc2a63908e5f61b010a +timeCreated: 1517853205 \ No newline at end of file diff --git a/Unity.Transforms2D/Heading2DComponent.cs b/Unity.Transforms2D/Heading2DComponent.cs new file mode 100644 index 00000000..dc0e0957 --- /dev/null +++ b/Unity.Transforms2D/Heading2DComponent.cs @@ -0,0 +1,18 @@ +using System; +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms2D +{ + /// + /// Direction vector representing heading. + /// If present, used to build TransformMatrix (if also present), where Y is set to zero. + /// + [Serializable] + public struct Heading2D : IComponentData + { + public float2 Value; + } + + public class Heading2DComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms2D/Heading2DComponent.cs.meta b/Unity.Transforms2D/Heading2DComponent.cs.meta new file mode 100644 index 00000000..9ad2bf50 --- /dev/null +++ b/Unity.Transforms2D/Heading2DComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5fdcf73cf7db45e0a744d4c26ec07ba7 +timeCreated: 1517853091 \ No newline at end of file diff --git a/Unity.Transforms2D/MoveForward2DSystem.cs b/Unity.Transforms2D/MoveForward2DSystem.cs new file mode 100644 index 00000000..ef098b23 --- /dev/null +++ b/Unity.Transforms2D/MoveForward2DSystem.cs @@ -0,0 +1,30 @@ +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Transforms; +using UnityEngine; +using UnityEngine.Scripting; + +namespace Unity.Transforms2D +{ + [Preserve] + public class MoveForward2DSystem : JobComponentSystem + { + [ComputeJobOptimization] + struct MoveForwardPosition : IJobProcessComponentData + { + public float dt; + + public void Execute(ref Position2D position, [ReadOnly]ref Heading2D heading, [ReadOnly]ref MoveSpeed moveSpeed) + { + position.Value += dt * moveSpeed.speed * heading.Value; + } + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + var moveForwardPositionJob = new MoveForwardPosition { dt = Time.deltaTime }; + return moveForwardPositionJob.Schedule(this, 64, inputDeps); + } + } +} diff --git a/Unity.Transforms2D/MoveForward2DSystem.cs.meta b/Unity.Transforms2D/MoveForward2DSystem.cs.meta new file mode 100644 index 00000000..a0bf27de --- /dev/null +++ b/Unity.Transforms2D/MoveForward2DSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6fd33a8673a1464e8a25173f09687acd +timeCreated: 1517857247 \ No newline at end of file diff --git a/Unity.Transforms2D/Position2DComponent.cs b/Unity.Transforms2D/Position2DComponent.cs new file mode 100644 index 00000000..cc526d17 --- /dev/null +++ b/Unity.Transforms2D/Position2DComponent.cs @@ -0,0 +1,18 @@ +using System; +using Unity.Entities; +using Unity.Mathematics; + +namespace Unity.Transforms2D +{ + /// + /// World position in 2D. + /// If present, used to build TransformMatrix (if also present), where Y is set to zero. + /// + [Serializable] + public struct Position2D : IComponentData + { + public float2 Value; + } + + public class Position2DComponent : ComponentDataWrapper { } +} diff --git a/Unity.Transforms2D/Position2DComponent.cs.meta b/Unity.Transforms2D/Position2DComponent.cs.meta new file mode 100644 index 00000000..875b13d2 --- /dev/null +++ b/Unity.Transforms2D/Position2DComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 927e7f75370854243a5ed8aa73d2ab03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity.Transforms2D/Transform2DSystem.cs b/Unity.Transforms2D/Transform2DSystem.cs new file mode 100644 index 00000000..18036a9d --- /dev/null +++ b/Unity.Transforms2D/Transform2DSystem.cs @@ -0,0 +1,88 @@ +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine.Scripting; + +namespace Unity.Transforms2D +{ + [Preserve] + public class Transform2DSystem : JobComponentSystem + { + struct TransGroup + { + public ComponentDataArray matrices; + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public SubtractiveComponent headings; + public int Length; + } + + [Inject] TransGroup m_TransGroup; + + struct RotTransGroup + { + public ComponentDataArray matrices; + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public ComponentDataArray headings; + public int Length; + } + + [Inject] RotTransGroup m_RotTransGroup; + + [ComputeJobOptimization] + struct TransToMatrix : IJobParallelFor + { + [ReadOnly] public ComponentDataArray positions; + public ComponentDataArray matrices; + + public void Execute(int i) + { + var position = positions[i].Value; + matrices[i] = new TransformMatrix + { + Value = math.translate(new float3(position.x,0.0f,position.y)) + }; + } + } + + [ComputeJobOptimization] + struct RotTransToMatrix : IJobParallelFor + { + [ReadOnly] public ComponentDataArray positions; + [ReadOnly] public ComponentDataArray headings; + public ComponentDataArray matrices; + + public void Execute(int i) + { + float2 position = positions[i].Value; + float2 heading = math.normalize(headings[i].Value); + matrices[i] = new TransformMatrix + { + Value = new float4x4 + { + m0 = new float4( heading.y, 0.0f, -heading.x, 0.0f ), + m1 = new float4( 0.0f, 1.0f, 0.0f, 0.0f ), + m2 = new float4( heading.x, 0.0f, heading.y, 0.0f ), + m3 = new float4( position.x, 0.0f, position.y, 1.0f ) + } + }; + } + } + + protected override JobHandle OnUpdate(JobHandle inputDeps) + { + var transToMatrixJob = new TransToMatrix(); + transToMatrixJob.positions = m_TransGroup.positions; + transToMatrixJob.matrices = m_TransGroup.matrices; + var transToMatrixJobHandle = transToMatrixJob.Schedule(m_TransGroup.Length, 64, inputDeps); + + var rotTransToMatrixJob = new RotTransToMatrix(); + rotTransToMatrixJob.positions = m_RotTransGroup.positions; + rotTransToMatrixJob.matrices = m_RotTransGroup.matrices; + rotTransToMatrixJob.headings = m_RotTransGroup.headings; + + return rotTransToMatrixJob.Schedule(m_RotTransGroup.Length, 64, transToMatrixJobHandle); + } + } +} diff --git a/Unity.Transforms2D/Transform2DSystem.cs.meta b/Unity.Transforms2D/Transform2DSystem.cs.meta new file mode 100644 index 00000000..94291701 --- /dev/null +++ b/Unity.Transforms2D/Transform2DSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7dbf17a7fcf44f73834ae7f83a81e798 +timeCreated: 1517853274 \ No newline at end of file diff --git a/Unity.Transforms2D/Unity.Transforms2D.asmdef b/Unity.Transforms2D/Unity.Transforms2D.asmdef new file mode 100644 index 00000000..4aadff6b --- /dev/null +++ b/Unity.Transforms2D/Unity.Transforms2D.asmdef @@ -0,0 +1,15 @@ +{ + "name": "Unity.Transforms2D", + "references": [ + "Unity.Entities", + "Unity.Entities.Hybrid", + "Unity.Transforms", + "Unity.Collections", + "Unity.Burst", + "Unity.Jobs", + "Unity.Mathematics" + ], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [] +} diff --git a/Unity.Transforms2D/Unity.Transforms2D.asmdef.meta b/Unity.Transforms2D/Unity.Transforms2D.asmdef.meta new file mode 100644 index 00000000..00ed7244 --- /dev/null +++ b/Unity.Transforms2D/Unity.Transforms2D.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 27779cde6c92c41abb406be1c77301f6 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 00000000..5fded592 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "keywords": [ + "entities", + "unity" + ], + "version": "0.0.12-preview.2", + "name": "com.unity.entities", + "dependencies": { + "com.unity.collections": "0.0.9-preview.1", + "com.unity.properties": "0.1.18-preview.2", + "com.unity.burst": "0.2.4-preview.5", + "com.unity.jobs": "0.0.7-preview.1", + "com.unity.mathematics": "0.0.12-preview.2" + }, + "description": "Unity Entity Component System - Core Entity Component System, New Transform components, basic Instance Mesh Renderer" +} \ No newline at end of file diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 00000000..8657c76a --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3eaa0563fc0ba44faae08db3e076afd4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: