├── -Tests.meta ├── -Tests ├── E7.E7ECS.Tests.asmdef ├── E7.E7ECS.Tests.asmdef.meta ├── TestTravel.cs └── TestTravel.cs.meta ├── Assert.meta ├── Assert ├── README.md └── README.md.meta ├── CopyLTWToGameObject.meta ├── CopyLTWToGameObject ├── CopyLTWToGameObject.cs ├── CopyLTWToGameObject.cs.meta ├── CopyLTWToGameObjectComponent.cs ├── CopyLTWToGameObjectComponent.cs.meta ├── CopyLTWToGameObjectSystem.cs └── CopyLTWToGameObjectSystem.cs.meta ├── DataStructure.meta ├── DataStructure ├── BlittableBool.cs ├── BlittableBool.cs.meta ├── DateTimeTicks.cs ├── DateTimeTicks.cs.meta ├── PointTracker.meta ├── PointTracker │ ├── PointTracker.cs │ ├── PointTracker.cs.meta │ ├── README.md │ └── README.md.meta ├── ReadOnlyTouch.meta ├── ReadOnlyTouch │ ├── README.md │ ├── README.md.meta │ ├── ReadOnlyTouch.cs │ └── ReadOnlyTouch.cs.meta ├── Sentity.cs ├── Sentity.cs.meta ├── Travel.meta └── Travel │ ├── README.md │ ├── README.md.meta │ ├── Travel.cs │ └── Travel.cs.meta ├── E7.E7ECS.asmdef ├── E7.E7ECS.asmdef.meta ├── Extensions.meta ├── Extensions ├── DynamicBufferAsStringExtension.meta ├── DynamicBufferAsStringExtension │ ├── DynamicBufferAsStringExtension.cs │ ├── DynamicBufferAsStringExtension.cs.meta │ ├── README.md │ └── README.md.meta ├── MathematicsExtension.cs ├── MathematicsExtension.cs.meta ├── NativeArrayExtension.cs ├── NativeArrayExtension.cs.meta ├── README.md └── README.md.meta ├── LICENSE ├── LICENSE.meta ├── MessagingSystems.meta ├── MessagingSystems ├── DestroyMessageSystem.cs ├── DestroyMessageSystem.cs.meta ├── MessageArchetype.cs ├── MessageArchetype.cs.meta ├── MessageECBExtension.cs ├── MessageECBExtension.cs.meta ├── MessageEMExtension.cs ├── MessageEMExtension.cs.meta ├── MessageInterfaces.cs ├── MessageInterfaces.cs.meta ├── MessageUtility.cs ├── MessageUtility.cs.meta ├── README.md ├── README.md.meta ├── ReactiveCS.cs └── ReactiveCS.cs.meta ├── README.md ├── README.md.meta ├── Serialization.meta ├── Serialization ├── RealStreamBinaryReader.cs ├── RealStreamBinaryReader.cs.meta ├── RealStreamBinaryWriter.cs └── RealStreamBinaryWriter.cs.meta ├── SingleQuery.meta ├── SingleQuery ├── README.md ├── README.md.meta ├── SingleComponentQuery.cs ├── SingleComponentQuery.cs.meta ├── SingleQuery.cs ├── SingleQuery.cs.meta ├── SingleQueryRW.cs └── SingleQueryRW.cs.meta ├── UtilitySystems.meta ├── UtilitySystems ├── EntityCloningSystem.cs ├── EntityCloningSystem.cs.meta ├── PurifierSystem.cs ├── PurifierSystem.cs.meta ├── README.md ├── README.md.meta ├── VersionBumperSystem.cs └── VersionBumperSystem.cs.meta ├── WorldHelper.meta ├── WorldHelper ├── README.md ├── README.md.meta ├── WorldHelper.cs └── WorldHelper.cs.meta ├── package.json └── package.json.meta /-Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a785a6c2649a1429e8772c1f15f8d539 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /-Tests/E7.E7ECS.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "E7.E7ECS.Tests", 3 | "references": [ 4 | "E7.E7ECS" 5 | ], 6 | "optionalUnityReferences": [ 7 | "TestAssemblies" 8 | ], 9 | "includePlatforms": [ 10 | "Editor" 11 | ], 12 | "excludePlatforms": [], 13 | "allowUnsafeCode": false, 14 | "overrideReferences": false, 15 | "precompiledReferences": [], 16 | "autoReferenced": true, 17 | "defineConstraints": [], 18 | "versionDefines": [] 19 | } -------------------------------------------------------------------------------- /-Tests/E7.E7ECS.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b8fc7f97570bb419cacb98cc75e43432 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /-Tests/TestTravel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using NUnit.Framework; 6 | 7 | namespace E7Unity.Tests 8 | { 9 | public class TestTravel 10 | { 11 | [Test] 12 | public void Instantiating() 13 | { 14 | Travel t1 = new Travel(); 15 | Travel t2 = new Travel(); 16 | t1.Init(); 17 | t2.Init(); 18 | t1.Dispose(); 19 | t2.Dispose(); 20 | } 21 | 22 | private Travel MakeTravel 23 | { 24 | get{ 25 | Travel t = new Travel(); 26 | t.Init(); 27 | t.Add(0,0,500); 28 | t.Add(10,5,600); 29 | t.Add(20,5,700); 30 | t.Add(40,5,800); 31 | return t; 32 | } 33 | } 34 | 35 | [Test] 36 | public void Adding() 37 | { 38 | Travel t = MakeTravel; 39 | t.Dispose(); 40 | } 41 | 42 | [Test] 43 | public void FirstAndLast() 44 | { 45 | Travel t = MakeTravel; 46 | Assert.That(t.FirstEvent.Time, Is.Zero); 47 | Assert.That(t.FirstEvent.Position, Is.Zero); 48 | Assert.That(t.FirstData , Is.EqualTo(500)); 49 | 50 | Assert.That(t.LastEvent.Time, Is.EqualTo(15)); 51 | Assert.That(t.LastEvent.Position, Is.EqualTo(40)); 52 | Assert.That(t.LastData, Is.EqualTo(800)); 53 | t.Dispose(); 54 | } 55 | 56 | [Test] 57 | public void DataOfTime() 58 | { 59 | Travel t = MakeTravel; 60 | Assert.That(t.DataEventOfTime(6).data, Is.EqualTo(600)); 61 | Assert.That(t.DataEventOfTime(555).data, Is.EqualTo(800)); 62 | 63 | Assert.That(t.DataEventOfTime(6).travelEvent.Position, Is.EqualTo(10)); 64 | Assert.That(t.DataEventOfTime(555).travelEvent.Position, Is.EqualTo(40)); 65 | 66 | Assert.That(t.DataEventOfPosition(30).data, Is.EqualTo(700)); 67 | Assert.That(t.DataEventOfPosition(555).data, Is.EqualTo(800)); 68 | 69 | t.Dispose(); 70 | } 71 | 72 | [Test] 73 | public void AddDefaultAtZero() 74 | { 75 | Travel t = new Travel(); 76 | t.Init(); 77 | t.Add(10, 5, 600); 78 | t.Add(20, 5, 700); 79 | t.Add(40, 5, 800); 80 | Assert.That(t.DataEventOfTime(2).data, Is.EqualTo(default(int))); 81 | t.Dispose(); 82 | 83 | t = new Travel(); 84 | t.Init(); 85 | t.AddDefaultAtZero(555); 86 | Assert.That(t.DataEventOfTime(2).data, Is.EqualTo(555)); 87 | t.Add(10, 5, 600); 88 | t.Add(20, 5, 700); 89 | t.Add(40, 5, 800); 90 | t.AddDefaultAtZero(5555); 91 | Assert.That(t.DataEventOfTime(2).data, Is.EqualTo(555), "Still the same since there is already something at zero."); 92 | 93 | t.Dispose(); 94 | } 95 | 96 | [Test] 97 | public void NextPrevious() 98 | { 99 | Travel t = MakeTravel; 100 | TravelEvent te = t.DataEventOfPosition(30).travelEvent; 101 | Assert.That(t.NextOf(te).travelEvent.Position,Is.EqualTo(40) ); 102 | Assert.That(t.PreviousOf(te).travelEvent.Position, Is.EqualTo(10)); 103 | Assert.That(t.NextOf(te).data,Is.EqualTo(800) ); 104 | 105 | t.Dispose(); 106 | } 107 | 108 | } 109 | } -------------------------------------------------------------------------------- /-Tests/TestTravel.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0a64ed5326ad3452bae003b563cfe17b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assert.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b426f338bc82f4ef5977f91625a31077 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assert/README.md: -------------------------------------------------------------------------------- 1 | # Assert 2 | 3 | Some tools for unit testing. 4 | -------------------------------------------------------------------------------- /Assert/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 983c88b42bb6d4854a20119d53c1b3a6 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CopyLTWToGameObject.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7c2f0c20ed9a04324b987feb725670d7 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /CopyLTWToGameObject/CopyLTWToGameObject.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | 3 | namespace E7.ECS.AttachGameObject 4 | { 5 | public struct CopyLTWToGameObject : IComponentData 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CopyLTWToGameObject/CopyLTWToGameObject.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1163101f1bb564a6faff55911ace1a0f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CopyLTWToGameObject/CopyLTWToGameObjectComponent.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | 3 | namespace E7.ECS.AttachGameObject 4 | { 5 | public class CopyLTWToGameObjectComponent : ComponentDataProxy 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CopyLTWToGameObject/CopyLTWToGameObjectComponent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba09cc250a2164ae1a84bd15eb8ec9c7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CopyLTWToGameObject/CopyLTWToGameObjectSystem.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | using Unity.Entities; 3 | using Unity.Transforms; 4 | using Unity.Jobs; 5 | using UnityEngine.Jobs; 6 | using Unity.Burst; 7 | using Unity.Mathematics; 8 | using UnityEngine; 9 | 10 | namespace E7.ECS.AttachGameObject 11 | { 12 | /// 13 | /// CopyTransformToGameObjectSystem will copy PRS (not LTW, actually scale not supported too) 14 | /// which is a local transform in the case that it has any attaches. The correct copy should be global transform. 15 | /// 16 | /// This system allows the CALCULATED LTW from TransformSystem to be applied to game object. 17 | /// It runs after transform system to allow leaf object to calculate its world pos first before copying. 18 | /// 19 | /// The matrix is decoded very simply, you must not have projections encoded in the matrix. 20 | /// TODO : Currently decode only position and scale.. 21 | /// 22 | [ExecuteInEditMode] 23 | [UpdateInGroup(typeof(TransformSystemGroup))] 24 | [UpdateAfter(typeof(EndFrameLocalToParentSystem))] 25 | public class CopyLTWToGameObjectSystem : JobComponentSystem 26 | { 27 | [BurstCompile] 28 | struct CopyTransforms : IJobParallelForTransform 29 | { 30 | [ReadOnly] public ComponentDataFromEntity ltw; 31 | 32 | [ReadOnly] 33 | [DeallocateOnJobCompletion] 34 | public NativeArray entities; 35 | 36 | public void Execute(int index, TransformAccess transform) 37 | { 38 | var entity = entities[index]; 39 | float4x4 matrix = ltw[entity].Value; 40 | 41 | //Decode the matrix 42 | var translation = matrix.c3; 43 | transform.position = new Vector3(translation.x, translation.y, translation.z); 44 | 45 | var scale = new Vector3(math.length(matrix.c0), math.length(matrix.c1), math.length(matrix.c2)); 46 | transform.localScale = scale; 47 | } 48 | } 49 | 50 | EntityQuery m_TransformGroup; 51 | 52 | protected override void OnCreateManager() 53 | { 54 | m_TransformGroup = GetEntityQuery(ComponentType.ReadOnly(typeof(CopyLTWToGameObject)), typeof(UnityEngine.Transform)); 55 | } 56 | 57 | protected override JobHandle OnUpdate(JobHandle inputDeps) 58 | { 59 | var transforms = m_TransformGroup.GetTransformAccessArray(); 60 | var entities = m_TransformGroup.ToEntityArray(Allocator.TempJob); 61 | 62 | var copyTransformsJob = new CopyTransforms 63 | { 64 | ltw = GetComponentDataFromEntity(isReadOnly: true), 65 | entities = entities 66 | }; 67 | 68 | return copyTransformsJob.Schedule(transforms, inputDeps); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /CopyLTWToGameObject/CopyLTWToGameObjectSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b6bdf31cab80d437d89c051aec4cf01d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /DataStructure.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2af70492b4be848fda6119eb1756115b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /DataStructure/BlittableBool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using System; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | /// 10 | /// Welcome to the "I have put a bool into an IComponentData before" club. 11 | /// 12 | [Serializable] 13 | public struct Bool 14 | { 15 | [SerializeField] private byte Value; 16 | 17 | public static implicit operator Bool(bool b) => new Bool() { Value = Convert.ToByte(b) }; 18 | public static implicit operator bool(Bool b) => b.Value == 1; 19 | 20 | public override string ToString() => Value == 1 ? "true" : "false"; 21 | public override int GetHashCode() => Value.GetHashCode(); 22 | } 23 | 24 | #if UNITY_EDITOR 25 | [CustomPropertyDrawer(typeof(Bool))] 26 | public class BlittableBoolDrawer : PropertyDrawer 27 | { 28 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 29 | { 30 | EditorGUI.BeginProperty(position, label, property); 31 | property.Next(true); 32 | property.intValue = (EditorGUI.Toggle(position, label, property.intValue == 1 ? true : false) ? 1 : 0); 33 | EditorGUI.EndProperty(); 34 | } 35 | } 36 | #endif -------------------------------------------------------------------------------- /DataStructure/BlittableBool.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: efa8fb8f6aa244cb5a78904311b0fb52 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /DataStructure/DateTimeTicks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public struct DateTimeTicks 4 | { 5 | public long ticks; 6 | public static explicit operator DateTime(DateTimeTicks dte) => new DateTime(dte.ticks, DateTimeKind.Utc); 7 | public static explicit operator DateTimeTicks(DateTime dt) => new DateTimeTicks { ticks = dt.Ticks }; 8 | public static DateTimeTicks Now() => (DateTimeTicks)DateTime.UtcNow; 9 | } 10 | -------------------------------------------------------------------------------- /DataStructure/DateTimeTicks.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 630e0eb7d79874a18b340b51471cb194 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /DataStructure/PointTracker.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4d503a7bc33064a40b92045c43b688b6 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /DataStructure/PointTracker/PointTracker.cs: -------------------------------------------------------------------------------- 1 | //Uncomment this to debug native shenanigans that might happen 2 | //#define DEBUG_POINT_TRACKER 3 | 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using UnityEngine; 7 | using Unity.Entities; 8 | using Unity.Mathematics; 9 | using Unity.Collections; 10 | using Unity.Jobs; 11 | 12 | /// 13 | /// From a sequence of events that might lacks an ID but having previous point, 14 | /// or having just previous point but lacking an ID, we can infer more states of the touch. 15 | /// 16 | /// Plus it can keep an arbitrary boolean state for each holding down touch. 17 | /// 18 | public struct PointTracker : System.IDisposable 19 | { 20 | private NativeList registeredPoints; 21 | private NativeHashMap registeredStates; 22 | private NativeHashMap registeredTouchId; 23 | private NativeArray touchIdRunnerMemory; 24 | private int touchIdRunner 25 | { 26 | get => touchIdRunnerMemory[0]; 27 | set => touchIdRunnerMemory[0] = value; 28 | } 29 | 30 | /// 31 | /// Make sure even if player use all of fingers and toes he still could not crash the game... 32 | /// 33 | public const int maximumTouch = 21; 34 | 35 | public void Dispose() 36 | { 37 | registeredPoints.Dispose(); 38 | registeredStates.Dispose(); 39 | registeredTouchId.Dispose(); 40 | touchIdRunnerMemory.Dispose(); 41 | } 42 | 43 | public PointTracker(Allocator allocator) 44 | { 45 | registeredPoints = new NativeList(allocator); 46 | registeredStates = new NativeHashMap(maximumTouch, allocator); 47 | registeredTouchId = new NativeHashMap(maximumTouch, allocator); 48 | touchIdRunnerMemory = new NativeArray(1, allocator); 49 | touchIdRunner = 0; 50 | } 51 | 52 | /// 53 | /// All points in this list are currently "down". Not array for performance reason so don't modify the list! Just read it! 54 | /// 55 | public IEnumerable CurrentPoints 56 | { 57 | get 58 | { 59 | for (int i = 0; i < registeredPoints.Length; i++) 60 | { 61 | yield return registeredPoints[i]; 62 | } 63 | } 64 | } 65 | 66 | /// 67 | /// If you are in a job get this and iterate on it instead of `CurrentPoints`. 68 | /// 69 | public NativeList CurrentPointsBurst => registeredPoints; 70 | 71 | /// 72 | /// You can keep whatever state you want with a bool per point. 73 | /// 74 | public Bool StateOfPoint(float2 point) 75 | { 76 | point = RoundVector(point); 77 | Bool ret; 78 | if (registeredStates.TryGetValue(point, out ret)) 79 | { 80 | //DebugLog($"State of {point.x} {point.y} is {ret}", LogType.Log); 81 | return ret; 82 | } 83 | else 84 | { 85 | //DebugLog($"State of {point.x} {point.y} not found", LogType.Log); 86 | return false; 87 | } 88 | } 89 | 90 | /// 91 | /// Each touch gets a unique generated ID that is carried over from point to point. 92 | /// So you know a new touch in other frame is perhaps the same ones from earlier frame. 93 | /// 94 | public int IdOfPoint(float2 point) 95 | { 96 | point = RoundVector(point); 97 | int ret; 98 | if (registeredTouchId.TryGetValue(point, out ret)) 99 | { 100 | //DebugLog($"Id of {point.x} {point.y} is {ret}", LogType.Log); 101 | return ret; 102 | } 103 | else 104 | { 105 | //DebugLog($"Id of {point.x} {point.y} not found", LogType.Log); 106 | return -1; 107 | } 108 | } 109 | 110 | public void Reset() 111 | { 112 | registeredPoints.Clear(); 113 | registeredStates.Clear(); 114 | registeredTouchId.Clear(); 115 | } 116 | 117 | public void Down(float2 pointDown) 118 | { 119 | pointDown = RoundVector(pointDown); 120 | #if DEBUG_POINT_TRACKER 121 | DebugLog($"Down {pointDown.x} {pointDown.y} ID : {touchIdRunner}", LogType.Log); 122 | #endif 123 | registeredPoints.Add(pointDown); 124 | registeredStates.TryAdd(pointDown, false); 125 | registeredTouchId.TryAdd(pointDown, touchIdRunner); 126 | touchIdRunner = touchIdRunner + 1; 127 | } 128 | 129 | public void SetState(float2 pointNow, bool toState) 130 | { 131 | pointNow = RoundVector(pointNow); 132 | if (registeredPoints.Contains(pointNow) && registeredStates.TryGetValue(pointNow, out _)) 133 | { 134 | //Debug.Log($"Set state OK {point.x} {point.y} {toState}"); 135 | registeredStates.Remove(pointNow); 136 | registeredStates.TryAdd(pointNow, toState); 137 | } 138 | #if DEBUG_POINT_TRACKER 139 | else 140 | { 141 | #if DEBUG_POINT_TRACKER 142 | DebugLog($"Set state fail {pointNow.x} {pointNow.y} {toState}", LogType.Log); 143 | #endif 144 | } 145 | #endif 146 | } 147 | 148 | /// 149 | /// This is just to fight with Unity's floating point weirdness 150 | /// 151 | private float2 RoundVector(float2 vector) => new float2(math.round(vector.x), math.round(vector.y)); 152 | 153 | public bool Move(float2 pointNow, float2 pointPrevious) 154 | { 155 | pointNow = RoundVector(pointNow); 156 | pointPrevious = RoundVector(pointPrevious); 157 | 158 | #if DEBUG_POINT_TRACKER 159 | DebugLog($"Move {pointNow.x} {pointNow.y} {pointPrevious.x} {pointPrevious.y}", LogType.Log); 160 | #endif 161 | 162 | #if UNITY_IOS 163 | if (pointNow == pointPrevious) 164 | { 165 | //This weird bug iOS reports happen after an errornous Up.. we interpret this as Down. 166 | 167 | #if DEBUG_POINT_TRACKER 168 | DebugLog($"Error Move!! {pointNow.x} {pointNow.y} {pointPrevious.x} {pointPrevious.y}", LogType.Error); 169 | #endif 170 | Down(pointNow); 171 | return true; 172 | } 173 | #endif 174 | 175 | Bool state; 176 | int touchId; 177 | 178 | Bool containsPrevious = registeredPoints.Contains(pointPrevious); 179 | 180 | if (containsPrevious) 181 | { 182 | float2 victim = pointPrevious; 183 | if (registeredStates.TryGetValue(victim, out state) && registeredTouchId.TryGetValue(victim, out touchId)) 184 | { 185 | registeredPoints.RemoveAtSwapBack(registeredPoints.IndexOf(victim)); 186 | registeredStates.Remove(victim); 187 | registeredTouchId.Remove(victim); 188 | registeredPoints.Add(pointNow); 189 | if (!registeredStates.TryGetValue(pointNow, out _) && !registeredTouchId.TryGetValue(pointNow, out _)) //somehow ArgumentException crash happen below!! 190 | { 191 | registeredStates.TryAdd(pointNow, state); //copy state 192 | registeredTouchId.TryAdd(pointNow, touchId); //copy touch ID too 193 | } 194 | return true; 195 | } 196 | } 197 | 198 | 199 | #if DEBUG_POINT_TRACKER 200 | DebugLog($"No such previous point! (move) {pointPrevious.x} x {pointPrevious.y}", LogType.Error); 201 | #endif 202 | return false; 203 | } 204 | 205 | public bool Up(float2 pointUp, float2 pointPrevious) 206 | { 207 | pointUp = RoundVector(pointUp); 208 | pointPrevious = RoundVector(pointPrevious); 209 | 210 | #if DEBUG_POINT_TRACKER 211 | DebugLog($"Up {pointUp.x} {pointUp.y} {pointPrevious.x} {pointPrevious.y}", LogType.Log); 212 | #endif 213 | 214 | //It has the same problem as Down 215 | bool containsPrevious = registeredPoints.Contains(pointPrevious); 216 | bool containsUp = false; 217 | 218 | if (!containsPrevious) 219 | { 220 | containsUp = registeredPoints.Contains(pointUp); 221 | } 222 | 223 | if (containsPrevious || containsUp ) 224 | { 225 | float2 victim = containsUp ? pointUp : pointPrevious; 226 | if (registeredStates.TryGetValue(victim, out _) && registeredTouchId.TryGetValue(victim, out _)) 227 | { 228 | registeredPoints.RemoveAtSwapBack(registeredPoints.IndexOf(victim)); 229 | registeredStates.Remove(victim); 230 | registeredTouchId.Remove(victim); 231 | return true; 232 | } 233 | } 234 | 235 | #if DEBUG_POINT_TRACKER 236 | DebugLog($"No such previous point! (up) {pointPrevious.x} x {pointPrevious.y}", LogType.Error); 237 | #endif 238 | return false; 239 | } 240 | 241 | private void DebugLog(string message, LogType logType) 242 | { 243 | #if DEBUG_POINT_TRACKER 244 | switch (logType) 245 | { 246 | case LogType.Log: { Debug.Log(message); return; } 247 | case LogType.Warning: { Debug.LogWarning(message); return; } 248 | case LogType.Error: { Debug.LogError(message); return; } 249 | default: { Debug.Log(message); return; } 250 | } 251 | #endif 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /DataStructure/PointTracker/PointTracker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3f107ff896d794b09b62868dc1d96b7c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /DataStructure/PointTracker/README.md: -------------------------------------------------------------------------------- 1 | # PointTracker 2 | 3 | You can ask "what is the current position of each point" from series of events. These event only includes "down", "move", and "up" but no "stationary". This class will attempts to determine the current state without knowing ID of any point, but by it's previous position when moved. -------------------------------------------------------------------------------- /DataStructure/PointTracker/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c680483cc93364cab9e082c0c0e57421 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /DataStructure/ReadOnlyTouch.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ef18b4d4bbf0a41e693ea749c78bf680 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /DataStructure/ReadOnlyTouch/README.md: -------------------------------------------------------------------------------- 1 | # ReadOnlyTouch 2 | 3 | With C# 7.2, use this with `in` parameter modifier to both pass this large struct as a reference and without the costly defensive copy. 4 | 5 | https://blogs.msdn.microsoft.com/seteplia/2018/03/07/the-in-modifier-and-the-readonly-structs-in-c/ 6 | -------------------------------------------------------------------------------- /DataStructure/ReadOnlyTouch/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: beb81191c618d46f78722762b2c84eda 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /DataStructure/ReadOnlyTouch/ReadOnlyTouch.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using Unity.Mathematics; 3 | 4 | /// 5 | /// With C# 7.2, use this with `in` parameter modifier to both pass this large struct as a reference and without the costly defensive copy. 6 | /// https://blogs.msdn.microsoft.com/seteplia/2018/03/07/the-in-modifier-and-the-readonly-structs-in-c/ 7 | /// 8 | /// Also it is a gibbed version of Unity ones. Less variables... 9 | /// 10 | public readonly struct ReadOnlyTouch 11 | { 12 | /// 13 | /// Convert from Unity Touch. 14 | /// 15 | public ReadOnlyTouch(in Touch t) : this( 16 | t.fingerId, 17 | t.position, 18 | t.deltaPosition, 19 | t.phase 20 | ) 21 | { } 22 | 23 | public ReadOnlyTouch( 24 | int fingerId, 25 | float2 position, 26 | float2 deltaPosition, 27 | TouchPhase phase 28 | ) 29 | { 30 | this.fingerId = fingerId; 31 | this.position = position; 32 | this.deltaPosition = deltaPosition; 33 | this.phase = phase; 34 | this.timestamp = 0; 35 | this.Valid = true; 36 | } 37 | 38 | public ReadOnlyTouch( 39 | int fingerId, 40 | float2 position, 41 | float2 deltaPosition, 42 | TouchPhase phase, 43 | double timestamp 44 | ) 45 | { 46 | this.fingerId = fingerId; 47 | this.position = position; 48 | this.deltaPosition = deltaPosition; 49 | this.phase = phase; 50 | this.timestamp = timestamp; 51 | this.Valid = true; 52 | } 53 | 54 | /// 55 | /// The unique index for the touch. 56 | /// 57 | public int fingerId { get; } 58 | /// 59 | /// The position of the touch in pixel coordinates. 60 | /// 61 | public float2 position { get; } 62 | /// 63 | /// The position delta since last change. 64 | /// 65 | public float2 deltaPosition { get; } 66 | /// 67 | /// Describes the phase of the touch. 68 | /// 69 | public TouchPhase phase { get; } 70 | 71 | public double timestamp { get; } 72 | 73 | public Bool Valid { get; } 74 | } -------------------------------------------------------------------------------- /DataStructure/ReadOnlyTouch/ReadOnlyTouch.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4baafd885595248be873e672f9adaf54 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /DataStructure/Sentity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unity.Entities; 3 | 4 | #if UNITY_EDITOR 5 | #endif 6 | 7 | /// 8 | /// C# serializable Entity.. lol 9 | /// 10 | [Serializable] 11 | public struct Sentity : IEquatable 12 | { 13 | public int Index; 14 | public int Version; 15 | 16 | public static bool operator ==(Sentity lhs, Sentity rhs) 17 | { 18 | return lhs.Index == rhs.Index && lhs.Version == rhs.Version; 19 | } 20 | 21 | public static bool operator !=(Sentity lhs, Sentity rhs) 22 | { 23 | return lhs.Index != rhs.Index || lhs.Version != rhs.Version; 24 | } 25 | 26 | public override bool Equals(object compare) 27 | { 28 | return this == (Sentity)compare; 29 | } 30 | 31 | public override int GetHashCode() 32 | { 33 | return Index; 34 | } 35 | 36 | public static Sentity Null => new Sentity(); 37 | 38 | public bool Equals(Sentity entity) 39 | { 40 | return entity.Index == Index && entity.Version == Version; 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return $"Sentity Index: {Index} Version: {Version}"; 46 | } 47 | 48 | public static explicit operator Sentity(Entity e) 49 | { 50 | return new Sentity { Index = e.Index, Version = e.Version }; 51 | } 52 | 53 | public static explicit operator Entity(Sentity e) 54 | { 55 | return new Entity { Index = e.Index, Version = e.Version }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DataStructure/Sentity.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 777c6b3b1c949494aa1cbaeb679d8964 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /DataStructure/Travel.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f9b25055aeaab471bb2378abf164b5ae 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /DataStructure/Travel/README.md: -------------------------------------------------------------------------------- 1 | # Travel 2 | 3 | A `Travel` data structure is a record of events that has **position** and **time**. The analogy is like a diary that records "I arrived at this position at this time". 4 | 5 | Each event is called a `TravelEvent`. The property is that they connects to the next one sequentially. As you add events to the `Travel` they are automatically connected. When adding, you specify its position and **elapsed time** from the latest one. (must be positive) 6 | 7 | Time and position together can infer velocity. If the time increase but position stays the same that could means there is no movement. If the time increase but position moves very little it could be that the velocity is low. 8 | 9 | After you have added all the events, there are many operations that you could efficiently do to find your data on the Travel more efficiently and easily than storing your data on the `List` and do a binary search. 10 | 11 | And you don't have to add position and time to your data by yourself, just state it while adding your data and it will be there. 12 | 13 | The questions are like : 14 | 15 | 1. What is the most recent event that happen before this time/this position? How far is it from the current time/position? 16 | 2. Search for an event that satisfies a predicate, but start the search from this time/position. 17 | 18 | PS. It uses C#6.0 syntax. I made it mainly for myself so I stopped caring about others using .NET 3.5 ... -------------------------------------------------------------------------------- /DataStructure/Travel/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9feb0230b7d12411fae7859dac4580ad 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /DataStructure/Travel/Travel.cs: -------------------------------------------------------------------------------- 1 | //#define TRAVEL_DEBUG 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEngine; 6 | 7 | using Unity.Entities; 8 | using Unity.Mathematics; 9 | using Unity.Jobs; 10 | using Unity.Collections; 11 | using System; 12 | 13 | public static class Travel 14 | { 15 | public static int DataIndexOfTime(NativeArray travelEvents, float time) 16 | { 17 | for (int i = 0; i < travelEvents.Length; i++) 18 | { 19 | if (travelEvents[i].IsTimeInRange(time)) 20 | { 21 | return i; 22 | } 23 | } 24 | return -1; 25 | } 26 | 27 | /// 28 | /// Use a special "expand out" search from remembered index. You can cache the previous search result and start from there! 29 | /// 30 | public static int DataIndexOfPosition(NativeList travelEvents, float position, ref int inputRememberIndex) 31 | { 32 | if (travelEvents.Length == 0) 33 | { 34 | return -1; 35 | } 36 | //Debug.Log($"Starting job {EventList.Length}"); 37 | int plusIndex = -1; 38 | int minusIndex = 0; 39 | int useIndex; 40 | int rememberIndex = inputRememberIndex; 41 | 42 | for (int i = 0; i < travelEvents.Length; i++) 43 | { 44 | if (i % 2 != 0) 45 | { 46 | if ((rememberIndex - (minusIndex + 1) >= 0)) 47 | { 48 | minusIndex++; 49 | useIndex = rememberIndex - minusIndex; 50 | } 51 | else 52 | { 53 | plusIndex++; 54 | useIndex = rememberIndex + plusIndex; 55 | } 56 | } 57 | else 58 | { 59 | if ((rememberIndex + (plusIndex + 1) < travelEvents.Length)) 60 | { 61 | plusIndex++; 62 | useIndex = rememberIndex + plusIndex; 63 | } 64 | else 65 | { 66 | minusIndex++; 67 | useIndex = rememberIndex - minusIndex; 68 | } 69 | } 70 | 71 | if (travelEvents[useIndex].IsPositionInRange(position)) 72 | { 73 | rememberIndex = useIndex; 74 | return useIndex; 75 | } 76 | } 77 | //Debug.Log("Ending with minus one"); 78 | return -1; 79 | } 80 | 81 | } 82 | 83 | /// 84 | /// Interval-based data storage. You can get the PREVIOUS data based on 85 | /// Travel can be in a job and burst compiled but not blittable as it contains `NativeList`. 86 | /// 87 | public struct Travel : System.IDisposable where T : struct 88 | { 89 | public void Dispose() 90 | { 91 | if (initialized) 92 | { 93 | travelEvents.Dispose(); 94 | datas.Dispose(); 95 | } 96 | } 97 | 98 | /// 99 | /// Only used for add default at zero check, but actually you can go to negative? 100 | /// 101 | private Bool HasEventAtZero { get; set; } 102 | 103 | private NativeList datas; 104 | private NativeList travelEvents; 105 | private int travelRememberIndex; 106 | 107 | //This two are for jobs. When you Add it needs to be realloc so if possible add everything before start using. 108 | Bool initialized; 109 | 110 | public void Init() 111 | { 112 | datas = new NativeList(Allocator.Persistent); 113 | travelEvents = new NativeList(Allocator.Persistent); 114 | initialized = true; 115 | } 116 | 117 | public void Clear() 118 | { 119 | HasEventAtZero = false; 120 | travelRememberIndex = 0; 121 | datas.Clear(); 122 | travelEvents.Clear(); 123 | } 124 | 125 | 126 | public T FirstData => datas.Length > 0 ? datas[0] : default(T); 127 | public T LastData => datas.Length > 0 ? datas[datas.Length - 1] : default(T); 128 | 129 | public TravelEvent FirstEvent 130 | { 131 | get 132 | { 133 | return travelEvents.Length > 0 ? travelEvents[0] : TravelEvent.INVALID; 134 | } 135 | } 136 | 137 | public TravelEvent LastEvent 138 | { 139 | get 140 | { 141 | return travelEvents.Length > 0 ? travelEvents[travelEvents.Length - 1] : TravelEvent.INVALID; 142 | } 143 | } 144 | 145 | private void SetLastEvent(TravelEvent te) 146 | { 147 | travelEvents.RemoveAtSwapBack(travelEvents.Length - 1); 148 | travelEvents.Add(te); 149 | } 150 | 151 | public bool NoEvent => travelEvents.Length == 0; 152 | 153 | /// 154 | /// Search until it found the interval with specified position. 155 | /// Note that in the case that position goes back and forth in time, it will just use the first one found. 156 | /// It will start the subsequent search from around the previous time's result. 157 | /// 158 | public (T data, TravelEvent travelEvent) DataEventOfPosition(float position) 159 | { 160 | //Debug.Log("Finding of " + position); 161 | int dataIndex = Travel.DataIndexOfPosition(travelEvents, position, ref travelRememberIndex); 162 | return DataEventOfPositionFromIndex(dataIndex); 163 | } 164 | 165 | public (T data, TravelEvent travelEvent) DataEventOfPositionFromIndex(int dataIndex) => dataIndex == -1 ? (default(T), TravelEvent.INVALID) : (datas[dataIndex], travelEvents[dataIndex]); 166 | 167 | public (T data, TravelEvent travelEvent) DataEventOfTime(float time) 168 | { 169 | int index = Travel.DataIndexOfTime(travelEvents, time); 170 | return index != -1 ? (datas[index], travelEvents[index]) : (default(T), TravelEvent.INVALID); 171 | } 172 | 173 | /// 174 | /// Adds event at position/time zero if there's nothing at zero yet. 175 | /// This prevents the travel returning null for all positive time and position 176 | /// 177 | /// If you have something at zero already this does nothing. 178 | /// 179 | /// BUT if you don't have anything at zero but have other data... it will crash 180 | /// Travel does not support adding a data in backward direction. 181 | /// 182 | public void AddDefaultAtZero(T data) 183 | { 184 | if (!HasEventAtZero) 185 | { 186 | #if TRAVEL_DEBUG 187 | Debug.Log("Adding default " + data.ToString()); 188 | #endif 189 | Add(0, 0, data); 190 | } 191 | } 192 | 193 | public (T data, TravelEvent travelEvent) NextOf(TravelEvent te) 194 | { 195 | int nextIndex = te.DataIndex + 1; 196 | if (nextIndex < datas.Length) 197 | { 198 | return (datas[nextIndex], travelEvents[nextIndex]); 199 | } 200 | else 201 | { 202 | return (default(T), TravelEvent.INVALID); 203 | } 204 | } 205 | 206 | public (T data, TravelEvent travelEvent) PreviousOf(TravelEvent te) 207 | { 208 | int prevIndex = te.DataIndex - 1; 209 | if (prevIndex >= 0 && datas.Length > 0) 210 | { 211 | return (datas[prevIndex], travelEvents[prevIndex]); 212 | } 213 | else 214 | { 215 | return (default(T), TravelEvent.INVALID); 216 | } 217 | } 218 | 219 | /// 220 | /// TIME IS TIME ELAPSED NOT ANY TIME YOU WANT!! 221 | /// 222 | public void Add(float position, float timeElapsed, T data) 223 | { 224 | #if TRAVEL_DEBUG 225 | Debug.Log($"Adding {position} {timeElapsed} {data.ToString()}"); 226 | #endif 227 | if (travelEvents.Length != 0 && timeElapsed <= 0) 228 | { 229 | throw new System.Exception($"Time elapsed must be positive, except the first one which can be zero. position : {position} timeElapsed : {timeElapsed}"); 230 | } 231 | TravelEvent lastEvent = LastEvent; 232 | TravelEvent newTe = new TravelEvent(position, (NoEvent ? 0 : lastEvent.Time) + timeElapsed, travelEvents.Length); 233 | if (!NoEvent) 234 | { 235 | lastEvent.LinkToNext(newTe, this); 236 | //Now we have to save back the last event too? 237 | SetLastEvent(lastEvent); 238 | } 239 | 240 | travelEvents.Add(newTe); 241 | datas.Add(data); 242 | 243 | if (position == 0) 244 | { 245 | HasEventAtZero = true; 246 | } 247 | } 248 | } 249 | 250 | /// 251 | /// To keep struct purity there's no data in here! Ask data with DataIndex from Travel! 252 | /// (It is kind of like LinkedList, but with purely struct data. 253 | /// 254 | public struct TravelEvent : IEquatable 255 | { 256 | //Because time must move forward just time being exact is enough? 257 | //Exact float ok I think 258 | public bool Equals(TravelEvent te) => Time == te.Time; 259 | 260 | public static TravelEvent INVALID => new TravelEvent(Mathf.NegativeInfinity, float.NegativeInfinity, -1); 261 | 262 | public bool Invalid => Position == INVALID.Position && Time == INVALID.Time; 263 | public bool Valid => !Invalid; 264 | 265 | public float Position { get; } 266 | /// 267 | /// When this is the last event, this is Mathf.Infinity 268 | /// 269 | public float PositionNext { get; private set; } 270 | 271 | public float Time { get; } 272 | /// 273 | /// When this is the last event, this is Mathf.Infinity 274 | /// 275 | public float TimeNext { get; private set; } 276 | 277 | public int DataIndex { get; } 278 | 279 | public TravelEvent(float absolutePosition, float time, int dataIndex) 280 | { 281 | this.Position = absolutePosition; 282 | this.PositionNext = Mathf.Infinity; 283 | this.Time = time; 284 | this.TimeNext = Mathf.Infinity; 285 | this.DataIndex = dataIndex; 286 | } 287 | 288 | internal void LinkToNext(TravelEvent te, Travel owningTravel) where T : struct 289 | { 290 | #if TRAVEL_DEBUG 291 | Debug.Log($"Linking to next : {te.Time} {te.Position}"); 292 | #endif 293 | this.TimeNext = te.Time; 294 | this.PositionNext = te.Position; 295 | //this.Next = te; 296 | //te.Previous = this; 297 | //Debug.Log("Link result : " + this.ToString()); 298 | } 299 | 300 | internal bool IsPositionInRange(float position) 301 | { 302 | #if TRAVEL_DEBUG 303 | Debug.Log($"Is in range? {Position} - {position} - {PositionNext}"); 304 | #endif 305 | //return (position >= Position) && (position < PositionNext); 306 | return (position >= Position) && (position < PositionNext); 307 | } 308 | 309 | internal bool IsTimeInRange(float time) 310 | { 311 | #if TRAVEL_DEBUG 312 | Debug.LogFormat($"Is time in range? {Time} - {time} - {TimeNext}"); 313 | #endif 314 | //return (time >= Time) && (time < TimeNext); 315 | return (time >= Time) && (time < TimeNext); 316 | } 317 | 318 | public override string ToString() 319 | { 320 | return string.Format($"P {Position}-{PositionNext} T {Time}-{TimeNext}"); 321 | } 322 | } -------------------------------------------------------------------------------- /DataStructure/Travel/Travel.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 178747d2281b8481b8a97f954f83bab5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /E7.E7ECS.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "E7.E7ECS", 3 | "references": [ 4 | "Unity.Burst", 5 | "Unity.Collections", 6 | "Unity.Entities", 7 | "Unity.Entities.Hybrid", 8 | "Unity.Transforms.Hybrid", 9 | "Unity.Rendering.Hybrid", 10 | "Unity.Transforms", 11 | "Unity.Mathematics", 12 | "Unity.Jobs" 13 | ], 14 | "optionalUnityReferences": [], 15 | "includePlatforms": [], 16 | "excludePlatforms": [], 17 | "allowUnsafeCode": true, 18 | "overrideReferences": false, 19 | "precompiledReferences": [], 20 | "autoReferenced": true, 21 | "defineConstraints": [], 22 | "versionDefines": [] 23 | } -------------------------------------------------------------------------------- /E7.E7ECS.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 399a18e0f8a594aaeb278fa49356d014 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Extensions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bb5b708630e6e4ae290e6c5810c9f5cf 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Extensions/DynamicBufferAsStringExtension.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 560e26e74cf4e47d5bafb692b0210a18 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Extensions/DynamicBufferAsStringExtension/DynamicBufferAsStringExtension.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using System.Text; 3 | 4 | public interface IStringBuffer { byte character { get; set; } } 5 | 6 | public static class DynamicBufferAsStringExtension 7 | { 8 | /// 9 | /// Currently allocate garbage. TODO: Go unsafe 10 | /// 11 | public static string AssembleString(this DynamicBuffer isb) 12 | where T : struct, IStringBuffer 13 | { 14 | byte[] chars = new byte[isb.Length]; 15 | for(int i = 0; i < isb.Length; i++) 16 | { 17 | chars[i] = isb[i].character; 18 | } 19 | return Encoding.UTF8.GetString(chars); 20 | } 21 | 22 | public static void AppendString(this DynamicBuffer isb, string s) 23 | where T : struct, IStringBuffer 24 | { 25 | byte[] bytes = Encoding.UTF8.GetBytes(s); 26 | foreach(byte b in bytes) 27 | { 28 | isb.Add(new T { character = b }); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Extensions/DynamicBufferAsStringExtension/DynamicBufferAsStringExtension.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f267443f3f34f4c82bc2294f55ff67d2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Extensions/DynamicBufferAsStringExtension/README.md: -------------------------------------------------------------------------------- 1 | # DynamicBufferAsStringExtension 2 | 3 | Your blittable `string` dream has come true. 4 | 5 | ## Declaration 6 | 7 | ```cs 8 | [InternalBufferCapacity(15)] // <-- Reserved string space per entity per chunk 9 | public struct NoteCharter : IStringBuffer, IBufferElementData { public byte character { get; set; } } 10 | 11 | [InternalBufferCapacity(10)] 12 | public struct ChartCommentCode : IStringBuffer, IBufferElementData { public byte character { get; set; } } 13 | 14 | [InternalBufferCapacity(20)] 15 | public struct ChartMetadata : IStringBuffer, IBufferElementData { public byte character { get; set; } } 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```cs 21 | Entity e = EntityManager.CreateEntity(); 22 | EntityManager.AddBuffer(e); 23 | var noteCharter = EntityManager.GetBuffer(e); 24 | noteCharter.AppendString(c.NoteCharterPt); //pog 25 | string s = noteCharter.AssembleString(); //pog 26 | ``` 27 | -------------------------------------------------------------------------------- /Extensions/DynamicBufferAsStringExtension/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d0ec56f65f6a64a02897f84521d645c5 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Extensions/MathematicsExtension.cs: -------------------------------------------------------------------------------- 1 | using Unity.Mathematics; 2 | using UnityEngine; 3 | 4 | namespace E7.ECS 5 | { 6 | public static class MathematicsExtension 7 | { 8 | /// 9 | /// Cannot be used in a Job! 10 | /// RectTransform has to be aligned completely flat to the screen. Discards all Z information. The purpose is to use `Rect` in a job because it is a struct. 11 | /// 12 | public static Rect ScreenRectOfRectTransform(RectTransform rt, Camera c) 13 | { 14 | Vector3[] fc = new Vector3[4]; 15 | rt.GetWorldCorners(fc); 16 | for (int a = 0; a < 3; a++) 17 | { 18 | fc[a] = Camera.main.WorldToScreenPoint(fc[a]); 19 | } 20 | return new Rect(fc[0].x, fc[0].y, fc[2].x - fc[1].x, fc[1].y - fc[0].y); 21 | } 22 | 23 | // public static Rect ScreenRectOfWorldBounds(Bounds bounds, Camera c) 24 | // { 25 | // rt.GetWorldCorners(fc); 26 | // for (int a = 0; a < 3; a++) 27 | // { 28 | // fc[a] = Camera.main.WorldToScreenPoint(fc[a]); 29 | // } 30 | // return new Rect(fc[0].x, fc[0].y, fc[2].x - fc[1].x, fc[1].y - fc[0].y); 31 | // } 32 | 33 | public static bool RectContains(in Rect rect, float2 point) 34 | { 35 | return (point.x >= rect.xMin) && (point.x < rect.xMax) && (point.y >= rect.yMin) && (point.y < rect.yMax); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Extensions/MathematicsExtension.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 915454eef2e0549d980a4cc6d3a3f807 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Extensions/NativeArrayExtension.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Mathematics; 3 | using Unity.Collections; 4 | using Unity.Jobs; 5 | using UnityEngine; 6 | using System.Collections.Generic; 7 | 8 | namespace E7.ECS 9 | { 10 | public static class NativeArrayExtension 11 | { 12 | public static List CopyToList(this NativeArray cda) where T : struct, IComponentData 13 | { 14 | using (var na = new NativeArray(cda.Length, Allocator.Temp)) 15 | { 16 | cda.CopyTo(na); 17 | List list = new List(na); 18 | return list; 19 | } 20 | } 21 | 22 | public static List CopyToList(this NativeArray cda, IComparer sorting) where T : struct, IComponentData 23 | { 24 | var list = CopyToList(cda); 25 | list.Sort(sorting); 26 | return list; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Extensions/NativeArrayExtension.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6aa68296943f14a79851157c404f5fb6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Extensions/README.md: -------------------------------------------------------------------------------- 1 | # Extensions 2 | 3 | Static methods or extension methods added to ECS. -------------------------------------------------------------------------------- /Extensions/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 72bcd7215b28a44a2b07072b0ac55c68 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sirawat Pitaksarit (5argon) - Exceed7 Experiments 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a98812a2a980b425785558bd86d1f7ce 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /MessagingSystems.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff2003bdb0ad247818f11bb0a59284aa 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /MessagingSystems/DestroyMessageSystem.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | using Unity.Burst; 5 | using UnityEngine.Jobs; 6 | 7 | namespace E7.ECS 8 | { 9 | /// 10 | /// Message will be destroyed all at 11 | /// 12 | [UpdateInGroup(typeof(InitializationSystemGroup))] 13 | public class DestroyMessageSystem : ComponentSystem 14 | { 15 | public struct MessageEntity : IComponentData { } 16 | 17 | EntityQuery cg; 18 | protected override void OnCreateManager() 19 | { 20 | cg = GetEntityQuery(ComponentType.ReadOnly()); 21 | } 22 | 23 | protected override void OnUpdate() 24 | { 25 | EntityManager.DestroyEntity(cg); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /MessagingSystems/DestroyMessageSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87232c98a573e4a0ea6eadba803f722c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /MessagingSystems/MessageArchetype.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | 3 | namespace E7.ECS 4 | { 5 | /// 6 | /// Just a wrapper that this is not an ordinary archetype 7 | /// 8 | public struct MessageArchetype 9 | { 10 | public EntityArchetype archetype; 11 | } 12 | } -------------------------------------------------------------------------------- /MessagingSystems/MessageArchetype.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0365c48beeda5494681b47177b97bae7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /MessagingSystems/MessageECBExtension.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | 3 | namespace E7.ECS 4 | { 5 | public static class MessageECBExtension 6 | { 7 | public static void Message(this EntityCommandBuffer.Concurrent ecb, int jobIndex, MessageArchetype msa, MessageComponent rx) 8 | where MessageComponent : struct, IMessage 9 | { 10 | Entity createdEntity = Message(ecb, jobIndex, msa); 11 | ecb.SetComponent(jobIndex, createdEntity, rx); 12 | } 13 | 14 | public static void Message(this EntityCommandBuffer ecb, MessageArchetype msa, MessageComponent rx) 15 | where MessageComponent : struct, IMessage 16 | { 17 | Entity createdEntity = Message(ecb, msa); 18 | ecb.SetComponent(createdEntity, rx); 19 | } 20 | 21 | public static Entity Message(this EntityCommandBuffer.Concurrent ecb, int jobIndex, MessageArchetype msa) 22 | { 23 | return ecb.CreateEntity(jobIndex, msa.archetype); 24 | } 25 | 26 | public static Entity Message(this EntityCommandBuffer ecb, MessageArchetype msa) 27 | { 28 | return ecb.CreateEntity(msa.archetype); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /MessagingSystems/MessageECBExtension.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1611d33aba81e46f5b120c9c404b8710 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /MessagingSystems/MessageEMExtension.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Collections; 3 | 4 | namespace E7.ECS 5 | { 6 | public static class MessageEMExtension 7 | { 8 | /// 9 | /// Now this supports zero-sized component. 10 | /// 11 | public static void UpsertComponentData(this EntityManager em, Entity entity) where T : struct, IComponentData 12 | { 13 | if (em.HasComponent(entity) == false) 14 | { 15 | em.AddComponent(entity,typeof(T)); 16 | } 17 | } 18 | 19 | public static void UpsertComponentData(this EntityManager em, Entity entity, T tagContent) where T : struct, IComponentData 20 | { 21 | if (em.HasComponent(entity) == false) 22 | { 23 | em.AddComponentData(entity, tagContent); 24 | } 25 | else 26 | { 27 | em.SetComponentData(entity, tagContent); 28 | } 29 | } 30 | 31 | public static bool TryGetComponent(this EntityManager em, Entity entity, out T componentData) where T : struct, IComponentData 32 | { 33 | if(em.HasComponent(entity)) 34 | { 35 | componentData = em.GetComponentData(entity); 36 | return true; 37 | } 38 | else 39 | { 40 | componentData = default; 41 | return false; 42 | } 43 | } 44 | 45 | public static MessageArchetype CreateMessageArchetype(this EntityManager em) 46 | where MessageComponent : struct, IMessage 47 | where MessageGroup : struct, IMessageGroup 48 | { 49 | return new MessageArchetype 50 | { 51 | archetype = em.CreateArchetype( 52 | ComponentType.ReadOnly(), 53 | ComponentType.ReadOnly(), 54 | ComponentType.ReadOnly() 55 | ) 56 | }; 57 | } 58 | 59 | public static void Message(this EntityManager ecb) 60 | where MessageComponent : struct, IMessage 61 | where MessageGroup : struct, IMessageGroup 62 | => Message(ecb, default, default); 63 | 64 | public static void Message(this EntityManager ecb, MessageComponent rx) 65 | where MessageComponent : struct, IMessage 66 | where MessageGroup : struct, IMessageGroup 67 | => Message(ecb, rx, default); 68 | 69 | private static void Message(this EntityManager ecb, MessageComponent rx, MessageGroup rg) 70 | where MessageComponent : struct, IMessage 71 | where MessageGroup : struct, IMessageGroup 72 | { 73 | //Debug.Log($"Issuing {typeof(ReactiveComponent).Name} (ECB)"); 74 | var e = ecb.CreateEntity(); 75 | ecb.AddComponentData(e, rx); 76 | ecb.AddComponentData(e, rg); 77 | ecb.AddComponentData(e, default); 78 | } 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /MessagingSystems/MessageEMExtension.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a5d986246a31349f9901d2211c1cbad9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /MessagingSystems/MessageInterfaces.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using UnityEngine; 3 | 4 | namespace E7.ECS 5 | { 6 | public interface IMessage : ITag { } 7 | public interface IMessageGroup : IComponentData { } 8 | public interface ITag : IComponentData { } 9 | } -------------------------------------------------------------------------------- /MessagingSystems/MessageInterfaces.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 51fe609aa09cf4b9eb638c592acd73b0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /MessagingSystems/MessageUtility.cs: -------------------------------------------------------------------------------- 1 | #define I_AM_WORRIED_ABOUT_EXECEPTION_PERFORMANCE 2 | 3 | using Unity.Entities; 4 | 5 | namespace E7.ECS 6 | { 7 | public static class MessageUtility 8 | { 9 | /// 10 | /// You can manually inject only one type of message of the group with the output of this to GetEntityQuery 11 | /// 12 | public static ComponentType[] GetMessageTypes() 13 | where Message : struct, IMessage 14 | where MessageGroup : struct, IMessageGroup 15 | { 16 | return new ComponentType[] { 17 | ComponentType.ReadOnly(), 18 | ComponentType.ReadOnly(), 19 | ComponentType.ReadOnly() 20 | }; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /MessagingSystems/MessageUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 304e04a6796844e3aa704eaea3f3e39d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /MessagingSystems/README.md: -------------------------------------------------------------------------------- 1 | # Messaging Systems 2 | 3 | A collection of systems and tools to enable a pattern like this : 4 | 5 | - `DestroyMessageSystem` (DMS) with no update order. Injects a component common to all messages and destroy them all. (It is a chunk destruction, should be fast) 6 | - Message sender use `EntityManager` or `EntityCommandBuffer` to create a message entity. I can expect systems after that barrier and before MDS to be able to handle the event right away, this is an advantage. 7 | - But disadvantage is the need of barriers, or else the message might end up too late. This will often breaks potential parallelism you could have had. 8 | - Also it could not afford to wait until the next frame by the way DMS work (it cleans up the message always at the end, after everyone had used the message. 9 | - Message sender must declare `[UpdateBefore(DMS)]` to explicitly express intent to send meaningful message. 10 | - Message receiver must declare `[UpdateBefore(DMS)]` and `[UpdateAfter(*message sender's barrier or just the message invoker in the case of PostUpdateCommand*)]`. 11 | - Systems before the message invoker have no chance to handle event. This is a weakness of this pattern, but I order the system carefully to mitigate this. 12 | - If you follow everything correctly, DMS even with no update order should be at the correct place, at around the end frame. It is a pain to type `UpdateBefore` requirement for all event users but I believe the auto arranged system loop should give me good parallelism and future proof when you add something more. 13 | - Message receivers may derive from a custom class with [UpdateBefore(DMS)] since attribute tag can be inherited to subclass. Along with this I added some helper methods to get the correct message (event). 14 | 15 | However it is advised to **not** use message pattern if possible. Behaviour should be driven by data in Data Oriented Design. For one-off things if you could detect the "switching" of data and do something based on that it would be better and more resilience (Maybe utilizing `ISystemStateComponentData` to remember things that you did). 16 | 17 | For example, if your character take damage and you want to display some effects, rather than sending a disposable `DamageMessage` from enemy system for the effect system to catch, you better have your effect system detect the delta in HP and act accordingly. (However you can feel that it sounds like a pain, which is why I made this in the first place.) -------------------------------------------------------------------------------- /MessagingSystems/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 43259336d3f494528aea2901847275b5 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /MessagingSystems/ReactiveCS.cs: -------------------------------------------------------------------------------- 1 | #define I_AM_WORRIED_ABOUT_EXECEPTION_PERFORMANCE 2 | 3 | using Unity.Entities; 4 | using Unity.Collections; 5 | using Unity.Jobs; 6 | 7 | namespace E7.ECS 8 | { 9 | /// 10 | /// It will iterate through all messages received so far this frame for you, 11 | /// and call for each message. 12 | /// 13 | /// While in , you use or to check 14 | /// the current iterating message and do things. 15 | /// 16 | public abstract class ReactiveCS : JobComponentSystem 17 | where MESSAGEGROUP : struct, IMessageGroup 18 | { 19 | EntityQuery messageGroup; 20 | protected override void OnCreateManager() 21 | { 22 | messageGroup = GetEntityQuery( 23 | ComponentType.ReadOnly(), 24 | ComponentType.ReadOnly() 25 | ); 26 | } 27 | 28 | protected virtual void OnBeforeAllMessages() { } 29 | protected virtual JobHandle OnAfterAllMessages(JobHandle inputDeps) => inputDeps; 30 | 31 | protected abstract void OnReaction(); 32 | protected override JobHandle OnUpdate(JobHandle inputDeps) 33 | { 34 | using (var na = messageGroup.ToEntityArray(Allocator.Temp)) 35 | { 36 | for (int i = 0; i < na.Length; i++) 37 | { 38 | iteratingEntity = na[i]; 39 | OnReaction(); 40 | } 41 | } 42 | return OnAfterAllMessages(inputDeps); 43 | } 44 | 45 | private protected Entity iteratingEntity; 46 | 47 | /// 48 | /// Use this overload if you are not going to use the `out` variable. (empty message content) Saves you a `GetComponentData`. 49 | /// 50 | protected bool ReactsTo() where T : struct, IMessage => EntityManager.HasComponent(iteratingEntity); 51 | 52 | protected bool ReactsTo(out T reactiveComponent) where T : struct, IMessage 53 | { 54 | //Debug.Log("Checking with " + typeof(T).Name); 55 | if (ReactsTo()) 56 | { 57 | reactiveComponent = EntityManager.GetComponentData(iteratingEntity); 58 | return true; 59 | } 60 | reactiveComponent = default; 61 | return false; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /MessagingSystems/ReactiveCS.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4043dabbeae9341069f0f1167d49acf7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # E7ECS 2 | 3 | Misc stuff I made for ECS. Not recommended using since the API is still changing and I will change them along the way. (too many junks right now imho) 4 | 5 | You can go read the readme in each folder! -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b28026e957b8b4e3a916e058cb60a114 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Serialization.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0771a8f083d7e416789d95728dbba7df 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Serialization/RealStreamBinaryReader.cs: -------------------------------------------------------------------------------- 1 | #define I_WANNA_CREATE_MY_OWN_WORLDS_BUT_GIVE_ME_THOSE_HOOKS 2 | 3 | using System; 4 | using System.IO; 5 | using Unity.Collections.LowLevel.Unsafe; 6 | using UnityEngine; 7 | using System.Linq; 8 | 9 | namespace E7.ECS 10 | { 11 | public unsafe class RealStreamBinaryReader : Unity.Entities.Serialization.BinaryReader 12 | { 13 | private Stream stream; 14 | private byte[] buffer; 15 | 16 | public RealStreamBinaryReader(Stream stream, int bufferSize = 65536) 17 | { 18 | this.stream = stream; 19 | buffer = new byte[bufferSize]; 20 | } 21 | 22 | public void Dispose() 23 | { 24 | stream.Dispose(); 25 | } 26 | 27 | public void ReadBytes(void* data, int bytes) 28 | { 29 | int remaining = bytes; 30 | int bufferSize = buffer.Length; 31 | 32 | fixed (byte* fixedBuffer = buffer) 33 | { 34 | while (remaining != 0) 35 | { 36 | int read = stream.Read(buffer, 0, Math.Min(remaining, bufferSize)); 37 | // Debug.Log($"Read {read} bytes"); 38 | // Debug.Log($"Content : { string.Join("|", buffer.Select(x => x.ToString()))}"); 39 | remaining -= read; 40 | UnsafeUtility.MemCpy(data, fixedBuffer, read); 41 | data = (byte*)data + read; 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Serialization/RealStreamBinaryReader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bd2cb7710fcac4f93921aa0ed04d61ba 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Serialization/RealStreamBinaryWriter.cs: -------------------------------------------------------------------------------- 1 | #define I_WANNA_CREATE_MY_OWN_WORLDS_BUT_GIVE_ME_THOSE_HOOKS 2 | 3 | using System; 4 | using System.IO; 5 | using Unity.Collections.LowLevel.Unsafe; 6 | 7 | namespace E7.ECS 8 | { 9 | public unsafe class RealStreamBinaryWriter : Unity.Entities.Serialization.BinaryWriter 10 | { 11 | private Stream stream; 12 | private byte[] buffer; 13 | 14 | public RealStreamBinaryWriter(Stream stream, int bufferSize = 65536) 15 | { 16 | this.stream = stream; 17 | buffer = new byte[bufferSize]; 18 | } 19 | 20 | public void Dispose() 21 | { 22 | stream.Dispose(); 23 | } 24 | 25 | public void WriteBytes(void* data, int bytes) 26 | { 27 | int remaining = bytes; 28 | int bufferSize = buffer.Length; 29 | 30 | fixed (byte* fixedBuffer = buffer) 31 | { 32 | while (remaining != 0) 33 | { 34 | int bytesToWrite = Math.Min(remaining, bufferSize); 35 | UnsafeUtility.MemCpy(fixedBuffer, data, bytesToWrite); 36 | stream.Write(buffer, 0, bytesToWrite); 37 | data = (byte*)data + bytesToWrite; 38 | remaining -= bytesToWrite; 39 | } 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Serialization/RealStreamBinaryWriter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c7cc61b6353a475399ca9f575768a82 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SingleQuery.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 98923c11a6e454b398b7c402242a8caa 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /SingleQuery/README.md: -------------------------------------------------------------------------------- 1 | # SingleQuery 2 | 3 | Special purpose chunk iteration wrapper based on ComponentGroup geared for only **one** IComponentData. 4 | 5 | Since using chunk iteration with things you are sure there is only one chunk, one component, or even one entity feels really awkward. By implementing on chunk iteration, we can be sure this will not be deprecated anytime soon. 6 | 7 | Requires registering with the system on OnCreateManager. -------------------------------------------------------------------------------- /SingleQuery/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 34eee577015124a51911fc5096adfed9 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /SingleQuery/SingleComponentQuery.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Collections.LowLevel.Unsafe; 3 | using UnityEngine; 4 | using System.Collections.Generic; 5 | using Unity.Collections; 6 | using System; 7 | 8 | namespace E7.ECS 9 | { 10 | /// 11 | /// Get a single or singleton managed component! 12 | /// 13 | public struct SingleComponentQuery 14 | where D : Component 15 | { 16 | [NativeSetClassTypeToNullOnSchedule] private EntityQuery cg; 17 | [NativeSetClassTypeToNullOnSchedule] private ComponentSystemBase system; 18 | public EntityQuery Query { get => cg; set => cg = value; } 19 | 20 | public static implicit operator ComponentType(SingleComponentQuery s) => ComponentType.ReadOnly(); 21 | public void Register(EntityQuery cg, ComponentSystemBase cs) 22 | { 23 | Query = cg; 24 | system = cs; 25 | //For managed component, it adds a hard update requirement. 26 | cs.RequireForUpdate(cg); 27 | } 28 | 29 | public D First 30 | { 31 | get{ 32 | var ca = cg.ToComponentArray(); 33 | if( ca.Length != 1) 34 | { 35 | throw new Exception($"You don't use {nameof(SingleComponentQuery)} when you have {ca.Length} {typeof(D).Name}!"); 36 | } 37 | return ca[0]; 38 | } 39 | } 40 | 41 | public IEnumerable GetComponentArrayIterator() 42 | { 43 | var ca = cg.ToComponentArray(); 44 | for (int i = 0; i < ca.Length; i++) 45 | { 46 | yield return ca[i]; 47 | } 48 | } 49 | 50 | /// 51 | /// Dispose the array too!! 52 | /// 53 | public NativeArray GetEntityArray(Allocator allocator) 54 | { 55 | return cg.ToEntityArray(allocator); 56 | } 57 | 58 | public bool Injected => Query.CalculateEntityCount() > 0; 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /SingleQuery/SingleComponentQuery.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3b535632677af4b1da197b88b90a7500 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SingleQuery/SingleQuery.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Collections.LowLevel.Unsafe; 3 | using Unity.Collections; 4 | using System; 5 | using Unity.Jobs; 6 | 7 | namespace E7.ECS 8 | { 9 | 10 | /// 11 | /// Special purpose chunk iteration wrapper based on ComponentGroup geared for only one IComponentData. 12 | /// Requires registering with the system on OnCreateManager. 13 | /// You can take it to the job after some ceremonies. 14 | /// 15 | public struct SingleQuery : IDisposable 16 | where D : struct, IComponentData 17 | { 18 | [NativeSetClassTypeToNullOnSchedule] private EntityQuery cg; 19 | [NativeSetClassTypeToNullOnSchedule] private ComponentSystemBase system; 20 | 21 | /// 22 | /// Useful when you want to send it to IJobChunk 23 | /// 24 | public EntityQuery Query { get => cg; set => cg = value; } 25 | 26 | /// 27 | /// Used in IJobChunk 28 | /// 29 | public ArchetypeChunkComponentType GetACCT() => system.GetArchetypeChunkComponentType(isReadOnly: true); 30 | 31 | /// 32 | /// Make itself known to the system's component groups. 33 | /// Pattern in your OnCreateManager is like : sq.Register(GetEntityQuery(sq)) 34 | /// 35 | public void Register(EntityQuery cg, ComponentSystemBase cs) 36 | { 37 | Query = cg; 38 | system = cs; 39 | } 40 | 41 | public int ComponentGroupLength => Query.CalculateEntityCount(); 42 | 43 | /// 44 | /// Only works in the main thread as it calculate component group's length 45 | /// 46 | public bool Injected => ComponentGroupLength > 0; 47 | 48 | public bool Any => NativeArrayOfChunk.Any; 49 | 50 | /// 51 | /// Very lazy method and only works in the main thread. 52 | /// 53 | public Entity FirstEntity 54 | { 55 | get 56 | { 57 | using (var ea = cg.ToEntityArray(Allocator.Temp)) 58 | { 59 | return ea[0]; 60 | } 61 | } 62 | } 63 | 64 | /// 65 | /// Prepare() in the main thread needed. 66 | /// 67 | public bool FirstChunkUnchanged => !NativeArrayOfChunk.FirstChunkChanged; 68 | 69 | /// 70 | /// Equivalent to multiple chunk iteration ceremonies that are needed in the main thread. Do it before sending itself to the job. 71 | /// If you Prepare to use in the main thread and not sending to the job, you have to Dispose manually. 72 | /// 73 | public SingleQuery Prepare(out JobHandle prepareJh) 74 | { 75 | var acct = GetACCT(); 76 | var aca = cg.CreateArchetypeChunkArray(Allocator.TempJob, out JobHandle pj); 77 | NativeArrayOfChunk = new ArchetypeChunkIterator { aca = aca, acct = acct, lastSystemVersionKeep = system.LastSystemVersion }; 78 | prepareJh = pj; 79 | return this; 80 | } 81 | 82 | /// 83 | /// Equivalent to multiple chunk iteration ceremonies that are needed in the main thread. Do it before sending itself to the job. 84 | /// If you Prepare to use in the main thread and not sending to the job, you have to Dispose manually. 85 | /// 86 | public SingleQuery Prepare() 87 | { 88 | var acct = GetACCT(); 89 | var aca = cg.CreateArchetypeChunkArray(Allocator.TempJob); 90 | NativeArrayOfChunk = new ArchetypeChunkIterator { aca = aca, acct = acct, lastSystemVersionKeep = system.LastSystemVersion }; 91 | return this; 92 | } 93 | 94 | /// 95 | /// Single typed chunk iterator. 96 | /// 97 | public struct ArchetypeChunkIterator 98 | { 99 | [ReadOnly] [DeallocateOnJobCompletion] public NativeArray aca; 100 | //Make this from out of job to be use in-job. 101 | [ReadOnly] public ArchetypeChunkComponentType acct; 102 | 103 | public uint lastSystemVersionKeep; 104 | 105 | //Makes `First` property more liberal to use. 106 | //The attribute allows this field to be empty at first in a job. 107 | [NativeDisableContainerSafetyRestriction] private NativeArray cachedFirstChunk; 108 | public NativeArray this[int chunkIndex] 109 | { 110 | get 111 | { 112 | bool firstChunk = chunkIndex == 0; 113 | if (firstChunk && cachedFirstChunk.IsCreated) 114 | { 115 | return cachedFirstChunk; 116 | } 117 | #if UNITY_EDITOR 118 | if (aca.IsCreated == false) 119 | { 120 | throw new Exception($"You didn't call .Prepare() yet before using..."); 121 | } 122 | #endif 123 | var na = aca[chunkIndex].GetNativeArray(acct); 124 | if (firstChunk) 125 | { 126 | cachedFirstChunk = na; 127 | } 128 | return na; 129 | 130 | } 131 | } 132 | 133 | public void Dispose() => aca.Dispose(); 134 | public bool Any => cachedFirstChunk.Length != 0; 135 | public bool IsCreated => aca.IsCreated; 136 | public bool FirstChunkChanged => ChunkChanged(0); 137 | public bool ChunkChanged(int i) => aca[i].DidChange(acct, lastSystemVersionKeep); 138 | } 139 | 140 | /// 141 | /// Do not call this if you are sending prepares to the job. 142 | /// Must call this if you prepare and use it in the main thread. 143 | /// 144 | public void Dispose() 145 | { 146 | NativeArrayOfChunk.Dispose(); 147 | } 148 | 149 | /// 150 | /// Valid after PrepareForRead/Write 151 | /// 152 | public int ChunkCount => NativeArrayOfChunk.aca.Length; 153 | 154 | /// 155 | /// Please iterate through `ChunkCount` as an indexer. 156 | /// 157 | public ArchetypeChunkIterator NativeArrayOfChunk { get; private set; } 158 | 159 | public D First => NativeArrayOfChunk[0][0]; 160 | 161 | /// 162 | /// When you sure your single component would all be together in one chunk you can iterate through them all with this. 163 | /// It can throw to let you know if that is not the case. 164 | /// 165 | public NativeArray FirstChunk 166 | { 167 | get 168 | { 169 | #if UNITY_EDITOR 170 | if (ChunkCount != 1) 171 | { 172 | throw new Exception($"You are using FirstChunk but chunk count is not 1 but {ChunkCount}. Probably you are expecting there is one chunk?"); 173 | } 174 | #endif 175 | return NativeArrayOfChunk[0]; 176 | } 177 | } 178 | 179 | /// 180 | /// Allows stunt flying in OnCreateManager. 181 | /// 182 | public static implicit operator ComponentType(SingleQuery s) => ComponentType.ReadOnly(); 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /SingleQuery/SingleQuery.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 15cc2ec3a3c254272ac6b0f5a69d2a94 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SingleQuery/SingleQueryRW.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Collections.LowLevel.Unsafe; 3 | using Unity.Collections; 4 | using System; 5 | 6 | namespace E7.ECS 7 | { 8 | /// 9 | /// Special purpose chunk iteration wrapper based on ComponentGroup geared for only one IComponentData. 10 | /// Requires registering with the system on OnCreateManager. 11 | /// You can take it to the job after some ceremonies. 12 | /// 13 | public struct SingleQueryRW : IDisposable 14 | where D : struct, IComponentData 15 | { 16 | [NativeSetClassTypeToNullOnSchedule] private EntityQuery cg; 17 | [NativeSetClassTypeToNullOnSchedule] private ComponentSystemBase system; 18 | 19 | /// 20 | /// Useful when you want to send it to IJobChunk 21 | /// 22 | public EntityQuery Query { get => cg; set => cg = value; } 23 | 24 | /// 25 | /// Used in IJobChunk 26 | /// 27 | public ArchetypeChunkComponentType GetACCT() => system.GetArchetypeChunkComponentType(isReadOnly: false); 28 | 29 | /// 30 | /// Make itself known to the system's component groups. 31 | /// Pattern in your OnCreateManager is like : sq.Register(GetEntityQuery(sq)) 32 | /// 33 | public void Register(EntityQuery cg, ComponentSystemBase cs) 34 | { 35 | Query = cg; 36 | system = cs; 37 | } 38 | 39 | public int ComponentGroupLength => Query.CalculateEntityCount(); 40 | 41 | /// 42 | /// Only works in the main thread as it calculate component group's length 43 | /// 44 | public bool Injected => ComponentGroupLength > 0; 45 | 46 | /// 47 | /// Very lazy method and only works in the main thread. 48 | /// 49 | public Entity FirstEntity 50 | { 51 | get 52 | { 53 | using (var ea = cg.ToEntityArray(Allocator.Temp)) 54 | { 55 | return ea[0]; 56 | } 57 | } 58 | } 59 | 60 | /// 61 | /// Prepare() in the main thread needed. 62 | /// 63 | public bool FirstChunkUnchanged => !NativeArrayOfChunk.FirstChunkChanged; 64 | 65 | private bool prepared; 66 | 67 | /// 68 | /// Equivalent to multiple chunk iteration ceremonies that are needed in the main thread. Do it before sending itself to the job. 69 | /// If you Prepare to use in the main thread and not sending to the job, you have to Dispose manually. 70 | /// 71 | public SingleQueryRW Prepare() 72 | { 73 | var acct = GetACCT(); 74 | var aca = cg.CreateArchetypeChunkArray(Allocator.TempJob); 75 | NativeArrayOfChunk = new ArchetypeChunkIterator { aca = aca, acct = acct, lastSystemVersionKeep = system.LastSystemVersion }; 76 | return this; 77 | } 78 | 79 | /// 80 | /// Do not call this if you are sending prepares to the job. 81 | /// Must call this if you prepare and use it in the main thread. 82 | /// 83 | public void Dispose() 84 | { 85 | NativeArrayOfChunk.Dispose(); 86 | } 87 | 88 | /// 89 | /// Valid after PrepareForRead/Write 90 | /// 91 | public int ChunkCount => NativeArrayOfChunk.aca.Length; 92 | 93 | /// 94 | /// Please iterate through `ChunkCount` as an indexer. 95 | /// 96 | public ArchetypeChunkIterator NativeArrayOfChunk { get; private set; } 97 | 98 | /// 99 | /// Single typed chunk iterator. 100 | /// 101 | public struct ArchetypeChunkIterator : IDisposable 102 | { 103 | [DeallocateOnJobCompletion] public NativeArray aca; 104 | //Make this from out of job to be use in-job. 105 | public ArchetypeChunkComponentType acct; 106 | 107 | public uint lastSystemVersionKeep; 108 | 109 | //Makes `First` property more liberal to use. 110 | //The attribute allows this field to be empty at first in a job. 111 | [NativeDisableContainerSafetyRestriction] private NativeArray cachedFirstChunk; 112 | public NativeArray this[int chunkIndex] 113 | { 114 | get 115 | { 116 | bool firstChunk = chunkIndex == 0; 117 | if (firstChunk && cachedFirstChunk.IsCreated) 118 | { 119 | return cachedFirstChunk; 120 | } 121 | #if UNITY_EDITOR 122 | if (aca.IsCreated == false) 123 | { 124 | throw new Exception($"You didn't call .Prepare() yet before using..."); 125 | } 126 | #endif 127 | var na = aca[chunkIndex].GetNativeArray(acct); 128 | if (firstChunk) 129 | { 130 | cachedFirstChunk = na; 131 | } 132 | return na; 133 | } 134 | } 135 | 136 | public void Dispose() => aca.Dispose(); 137 | public bool IsCreated => aca.IsCreated; 138 | public bool FirstChunkChanged => ChunkChanged(0); 139 | public bool ChunkChanged(int i) => aca[i].DidChange(acct, lastSystemVersionKeep); 140 | } 141 | 142 | /// 143 | /// The chunk is dirtied on using EITHER getter or setter. (cause "changed") 144 | /// 145 | public D First 146 | { 147 | get => NativeArrayOfChunk[0][0]; 148 | 149 | set 150 | { 151 | var na = NativeArrayOfChunk[0]; 152 | na[0] = value; 153 | } 154 | } 155 | 156 | /// 157 | /// When you sure your single component would all be together in one chunk you can iterate through them all with this. 158 | /// It can throw to let you know if that is not the case. 159 | /// The chunk is considered written immediately. 160 | /// 161 | public NativeArray FirstChunk 162 | { 163 | get 164 | { 165 | #if UNITY_EDITOR 166 | if (ChunkCount != 1) 167 | { 168 | throw new Exception($"You are using FirstChunk but chunk count is not 1 but {ChunkCount}. Probably you are expecting there is one chunk?"); 169 | } 170 | #endif 171 | return NativeArrayOfChunk[0]; 172 | } 173 | } 174 | 175 | /// 176 | /// Allows stunt flying in OnCreateManager. 177 | /// 178 | public static implicit operator ComponentType(SingleQueryRW s) => ComponentType.ReadWrite(); 179 | } 180 | 181 | } -------------------------------------------------------------------------------- /SingleQuery/SingleQueryRW.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c0eb1d964ffae49d6a6155c7a00db0d2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UtilitySystems.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 18a8067b3d4494124b75ee9af5d044fd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UtilitySystems/EntityCloningSystem.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Collections; 3 | 4 | namespace E7.ECS 5 | { 6 | /// 7 | /// Used by WorldHelper.CopyAllEntities 8 | /// 9 | public class EntityCloningSystem : ComponentSystem 10 | { 11 | internal struct Cloned : ISystemStateComponentData { } 12 | 13 | EntityQuery clonedGroup; 14 | protected override void OnCreateManager() 15 | { 16 | clonedGroup = GetEntityQuery(ComponentType.ReadOnly()); 17 | } 18 | 19 | /// 20 | /// Clone from the world this system resides in to any destination world. 21 | /// TODO : Add `EntityArchetypeQuery` support 22 | /// 23 | public void CloneTo(World destinationWorld) 24 | { 25 | EntityManager destinationEntityManager = destinationWorld.EntityManager; 26 | using (var ea = EntityManager.GetAllEntities(Allocator.Temp)) 27 | { 28 | for (int i = 0; i < ea.Length; i++) 29 | { 30 | Entity cloned = EntityManager.Instantiate(ea[i]); 31 | EntityManager.AddComponentData(cloned, new Cloned()); 32 | } 33 | } 34 | using (var remap = new NativeArray(clonedGroup.CalculateEntityCount(), Allocator.TempJob)) 35 | { 36 | destinationEntityManager.MoveEntitiesFrom(EntityManager, clonedGroup, remap); 37 | } 38 | var destEcs = destinationWorld.CreateSystem(); 39 | destEcs.CleanCloned(); 40 | destinationWorld.DestroySystem(destEcs); 41 | } 42 | 43 | 44 | internal void CleanCloned() 45 | { 46 | var enType = GetArchetypeChunkEntityType(); 47 | var aca = clonedGroup.CreateArchetypeChunkArray(Allocator.TempJob); 48 | for (int i = 0; i < aca.Length; i++) 49 | { 50 | var ea = aca[i].GetNativeArray(enType); 51 | for (int j = 0; j < ea.Length; j++) 52 | { 53 | EntityManager.RemoveComponent(ea[j]); 54 | } 55 | } 56 | } 57 | 58 | protected override void OnUpdate(){} 59 | } 60 | } -------------------------------------------------------------------------------- /UtilitySystems/EntityCloningSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b18e91b92b0d449888766133d20568b1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UtilitySystems/PurifierSystem.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Collections; 3 | using UnityEngine; 4 | using Unity.Jobs; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// Remove or set to default components of ALL entities in the world at once. 9 | /// It will work only on entities with that component. 10 | /// Subclass and call a series of Clean/Remove on your `OnUpdate`. 11 | /// 12 | /// When you are going to serialize a world for example, 13 | /// put a subclass of this system in that world and run Update 14 | /// once to clean up unwanted components. 15 | /// 16 | /// The clean or remove is based on whole-chunk operations. No iteration made. 17 | /// 18 | public abstract class PurifierSystem : ComponentSystem 19 | { 20 | protected void Clean() where T : struct, IComponentData 21 | { 22 | var cg = Entities.WithAll().ToEntityQuery(); 23 | EntityManager.RemoveComponent(cg, ComponentType.ReadOnly()); 24 | EntityManager.AddComponent(cg, ComponentType.ReadOnly()); 25 | } 26 | 27 | /// 28 | /// ISharedComponentData with default value is special. 29 | /// It does not generates `GameObject` dependency, yet the type is still in the Archetype. 30 | /// "Cleaning" them will do just that. Preserving archetype but does not care about its value. 31 | /// 32 | protected void CleanShared() where T : struct, ISharedComponentData 33 | { 34 | var cg = Entities.WithAll().ToEntityQuery(); 35 | EntityManager.RemoveComponent(cg, ComponentType.ReadOnly()); 36 | EntityManager.AddSharedComponentData(cg, default(T)); 37 | } 38 | 39 | protected void Remove() where T : struct 40 | { 41 | var cg = Entities.WithAll().ToEntityQuery(); 42 | EntityManager.RemoveComponent(cg, ComponentType.ReadOnly()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /UtilitySystems/PurifierSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fa3117c2ee16a45d68ffb8d0ae9174f8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UtilitySystems/README.md: -------------------------------------------------------------------------------- 1 | # UtilitySystems 2 | 3 | The beauty of system's modularity is that if you copy one of these files into your project it probably "just works" with your game's `World` and `Entity`. -------------------------------------------------------------------------------- /UtilitySystems/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d3e130d52b1084bef9dd6b13df4c489a 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /UtilitySystems/VersionBumperSystem.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | 3 | namespace E7.ECS 4 | { 5 | /// 6 | /// Its purpose of existence is just to increase the global version number on manual update. 7 | /// 8 | [DisableAutoCreation] 9 | internal class VersionBumperSystem : ComponentSystem 10 | { 11 | protected override void OnCreateManager() => this.Enabled = false; 12 | public void BumpVersion() 13 | { 14 | this.Enabled = true; 15 | Update(); 16 | this.Enabled = false; 17 | //Debug.Log($"Bumped to {GlobalSystemVersion}"); 18 | } 19 | protected override void OnUpdate() { } 20 | } 21 | } -------------------------------------------------------------------------------- /UtilitySystems/VersionBumperSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc55cd0d742074010912a0d1de959547 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /WorldHelper.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1c7d50a456fac4f10860e054221bd7dd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /WorldHelper/README.md: -------------------------------------------------------------------------------- 1 | # WorldHelper 2 | 3 | Tools! -------------------------------------------------------------------------------- /WorldHelper/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d5330f4e3ae07427f9d2bfdbece2884c 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /WorldHelper/WorldHelper.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using System; 3 | using System.Linq; 4 | using Unity.Mathematics; 5 | using UnityEngine.LowLevel; 6 | 7 | namespace E7.ECS 8 | { 9 | public static class WorldHelper 10 | { 11 | private const int noPayloadSize = 24; 12 | private const int withPayloadSize = 32; 13 | private const int multipleOf = 1024; 14 | public static int CalculateMinimumChunkSize(int createInstantiateDestroy, int remove, params (int sizeOf, int count)[] addSet) 15 | { 16 | var exactSize = 17 | (createInstantiateDestroy * noPayloadSize) + 18 | (remove * withPayloadSize) + 19 | addSet.Sum(x => (withPayloadSize + x.sizeOf) * x.count); 20 | 21 | return (int)(math.ceil(exactSize / (float)multipleOf) * multipleOf); 22 | } 23 | 24 | public static void IncreaseVersion() 25 | { 26 | World.DefaultGameObjectInjectionWorld.GetOrCreateSystem().BumpVersion(); 27 | } 28 | 29 | public static void CopyAllEntities(World fromWorld, World toWorld) 30 | { 31 | var ecs = fromWorld.CreateSystem(); 32 | ecs.CloneTo(toWorld); 33 | fromWorld.DestroySystem(ecs); 34 | } 35 | 36 | public static World CreateWorld(string name, params Type[] systemTypes) 37 | { 38 | World w = new World(name); 39 | return AddSystemsToWorld(w, systemTypes); 40 | } 41 | 42 | public static World AddSystemsToWorld(World w, params Type[] systemTypes) 43 | { 44 | foreach (var t in systemTypes) 45 | { 46 | if (!t.IsSubclassOf(typeof(ComponentSystemBase))) 47 | { 48 | throw new ArgumentException($"Hawawa! This type {t.Name} is not a system!"); 49 | } 50 | w.GetOrCreateSystem(t); 51 | } 52 | return w; 53 | } 54 | 55 | /// 56 | /// PlayerLoop will be DEFAULT player loop + your world. Any modification to the current loop are lost. 57 | /// 58 | public static void UseWorldAndSetAsActive(World w) 59 | { 60 | if (w != null) 61 | { 62 | World.DefaultGameObjectInjectionWorld = w; 63 | ScriptBehaviourUpdateOrder.UpdatePlayerLoop(w); 64 | } 65 | else 66 | { 67 | throw new Exception("You cannot use a null world.."); 68 | } 69 | } 70 | 71 | /// 72 | /// PlayerLoop will be back to default one. 73 | /// 74 | public static void DisposeAndStopUsingWorld(World w) 75 | { 76 | if (w != null) 77 | { 78 | World.DefaultGameObjectInjectionWorld = null; 79 | w.Dispose(); 80 | ScriptBehaviourUpdateOrder.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop()); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /WorldHelper/WorldHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e1be8bb1d959a44769dcb6325df803d4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.e7.e7ecs", 3 | "author": "Sirawat Pitaksarit / 5argon - Exceed7 Experiments", 4 | "displayName": "E7ECS", 5 | "version": "0.0.1", 6 | "unity": "2018.1", 7 | "description": "Misc Unity ECS codes." 8 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d25e7acc252a64fc4808e8405d31fa87 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------