├── .github └── workflows │ ├── ci-linux.yml │ ├── ci-macos.yml │ ├── ci-windows.yml │ └── markdown-link-check.yml ├── .gitignore ├── LICENSE ├── Package.swift ├── README.md └── Sources ├── MothECS-Demo └── main.swift └── MothECS ├── Moth+Component.swift ├── Moth+Entity.swift ├── Moth+Identifiers.swift ├── Moth+Mask.swift └── Moth.swift /.github/workflows/ci-linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | linux-build-release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Build Release 15 | run: swift build -c release 16 | -------------------------------------------------------------------------------- /.github/workflows/ci-macos.yml: -------------------------------------------------------------------------------- 1 | name: macOS 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | macos-build-release: 11 | runs-on: macos-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Build Release 15 | run: swift build -c release 16 | -------------------------------------------------------------------------------- /.github/workflows/ci-windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | windows-build-release: 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: compnerd/gha-setup-swift@main 15 | with: 16 | branch: swift-5.5-release 17 | tag: 5.5-RELEASE 18 | - name: Build Release 19 | run: swift build -c release -------------------------------------------------------------------------------- /.github/workflows/markdown-link-check.yml: -------------------------------------------------------------------------------- 1 | name: Check markdown links 2 | 3 | on: push 4 | 5 | jobs: 6 | markdown-link-check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: markdown-link-check 12 | uses: gaurav-nelson/github-action-markdown-link-check@master 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Junhao Wang (Forkercat) 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MothECS", 6 | 7 | products: [ 8 | .library( 9 | name: "MothECS", 10 | targets: ["MothECS"]), 11 | ], 12 | 13 | dependencies: [], 14 | 15 | targets: [ 16 | .target( 17 | name: "MothECS", 18 | dependencies: []), 19 | 20 | .target( 21 | name: "MothECS-Demo", 22 | dependencies: [ 23 | "MothECS" 24 | ]) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MothECS: Simple Entity Component System in Swift 📦 2 | 3 | [![macOS](https://github.com/forkercat/MothECS/actions/workflows/ci-macos.yml/badge.svg)](https://github.com/forkercat/MothECS/actions/workflows/ci-macos.yml) 4 | [![Linux](https://github.com/forkercat/MothECS/actions/workflows/ci-linux.yml/badge.svg)](https://github.com/forkercat/MothECS/actions/workflows/ci-linux.yml) 5 | [![Windows](https://github.com/forkercat/MothECS/actions/workflows/ci-windows.yml/badge.svg)](https://github.com/forkercat/MothECS/actions/workflows/ci-windows.yml) 6 | [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 7 | 8 | [![platform-compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fforkercat%2FMothECS%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/forkercat/MothECS) 9 | [![swift-version-compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fforkercat%2FMothECS%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/forkercat/MothECS) 10 | 11 | MothECS is a simple entity component system written in Swift. It supports the following features: 12 | 13 | - Use bitmask to manage relationship between entities and components 14 | - Support view operation with optional excepted type 15 | - Reuse destroyed entity in next creation 16 | 17 | Future Development: 18 | 19 | - Get entity ID from a component 20 | - Support more components (currently it supports 32) 21 | - Use sparse array 22 | 23 | [Moth](https://www.pinterest.com/pin/587649451369676935/) in MothECS - It refers to new players in [Sky](https://www.thatskygame.com/). Explanation is [here](https://www.reddit.com/r/SkyGame/comments/q03tj4/why_are_new_players_called_moths/)! 24 | 25 |

26 | This is moth! 27 |

28 | 29 | ## 🔧 Install 30 | 31 | You package file would be like: 32 | 33 | ```swift 34 | let package = Package( 35 | name: "YourPackageName", 36 | 37 | dependencies: [ 38 | .package(url: "https://github.com/forkercat/MothECS.git", .branch("main")), 39 | ], 40 | 41 | targets: [ 42 | // For Swift 5.5, use .executableTarget 43 | .target( 44 | name: "YourPackageName", 45 | dependencies: [ 46 | .product(name: "MothECS", package: "MothECS") 47 | ]), 48 | ] 49 | ) 50 | ``` 51 | 52 | 53 | ## 😆 Usage 54 | 55 | #### Import & Initialize 56 | 57 | ```swift 58 | import MothECS 59 | 60 | let moth = Moth() 61 | moth.log() // use this for showing bitmasks 62 | ``` 63 | 64 | #### Define Your Own Component Classes 65 | 66 | ```swift 67 | class TagComponent: MothComponent { 68 | required init() { } 69 | } 70 | 71 | class TransformComponent: MothComponent { 72 | required init() { } 73 | } 74 | 75 | class LightComponent: MothComponent { 76 | required init() { } 77 | } 78 | ``` 79 | 80 | #### Initialize & Create Entity 81 | 82 | ```swift 83 | let e0: MothEntityID = moth.createEntity() 84 | let e1: MothEntityID = moth.createEntity() 85 | let e2: MothEntityID = moth.createEntity() 86 | let e4: MothEntityID = moth.createEntity() 87 | assert(e0 != .invalid) 88 | _ = moth.entityIDs // return all valid entity IDs 89 | ``` 90 | 91 | #### Create & Assign Comonent 92 | 93 | ```swift 94 | moth.createComponent(TagComponent.self, to: e0) 95 | moth.createComponent(TransformComponent.self, to: e0) 96 | moth.createComponent(LightComponent.self, to: e0) // e0: [Tag, Transform, Light] 97 | 98 | moth.assignComponent(TagComponent(), to: e1) 99 | moth.assignComponent(TransformComponent(), to: e1) // e1: [Tag, Transform] 100 | moth.assignComponent(LightComponent(), to: e2) 101 | moth.assignComponent(TagComponent(), to: e2) // e2: [Tag, Light] 102 | ``` 103 | 104 | #### Remove Entity & Component 105 | 106 | ```swift 107 | moth.removeAllComponents(from: e4) // e4: [] 108 | moth.removeEntity(entityID: e4) // e4 is not invalid, its ID will be reused in next createEntity() 109 | moth.removeComponent(TagComponent.self, from: e2) // e2: [Light] 110 | ``` 111 | 112 | #### Get & Has Component 113 | 114 | ```swift 115 | if moth.hasComponent(LightComponent.self, in: e2) { 116 | _ = moth.getComponent(LightComponent.self, from: e2) // put getComponent() inside hasComponent() 117 | } 118 | ``` 119 | 120 | #### View Operation 121 | 122 | ```swift 123 | let v1 = moth.view(TagComponent.self, TransformComponent.self, LightComponent.self) 124 | let v2 = moth.view(TagComponent.self, TransformComponent.self) 125 | let v3 = moth.view(TagComponent.self, TransformComponent.self, excepts: LightComponent.self) 126 | 127 | // v1: [e0] 128 | // v2: [e0, e1] 129 | // v2: [e1] 130 | ``` 131 | 132 | 133 | ## 🙏 Reference 134 | 135 | - [How to make a simple entity-component-system in C++](https://www.david-colson.com/2020/02/09/making-a-simple-ecs.html) by David Colson 136 | - [fireblade-engine/ecs](https://github.com/fireblade-engine/ecs) by [@ctreffs](https://github.com/ctreffs) 137 | -------------------------------------------------------------------------------- /Sources/MothECS-Demo/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // MothECS-Demo 4 | // 5 | // Created by Junhao Wang on 1/12/22. 6 | // 7 | 8 | import MothECS 9 | 10 | // Define your own component classes 11 | class TagComponent: MothComponent { 12 | required init() { } 13 | } 14 | 15 | class TransformComponent: MothComponent { 16 | required init() { } 17 | } 18 | 19 | class LightComponent: MothComponent { 20 | required init() { } 21 | } 22 | 23 | let moth = Moth() 24 | 25 | // Create Entity 26 | let e0: MothEntityID = moth.createEntity() 27 | let e1: MothEntityID = moth.createEntity() 28 | let e2: MothEntityID = moth.createEntity() 29 | let e4: MothEntityID = moth.createEntity() 30 | 31 | assert(e0 != .invalid) 32 | 33 | print(moth.entityIDs) 34 | 35 | // Create Component 36 | moth.createComponent(TagComponent.self, to: e0) 37 | moth.createComponent(TransformComponent.self, to: e0) 38 | moth.createComponent(LightComponent.self, to: e0) // e0: [Tag, Transform, Light] 39 | 40 | // Assign Component 41 | moth.assignComponent(TagComponent(), to: e1) 42 | moth.assignComponent(TransformComponent(), to: e1) // e1: [Tag, Transform] 43 | moth.assignComponent(LightComponent(), to: e2) 44 | moth.assignComponent(TagComponent(), to: e2) // e2: [Tag, Light] 45 | 46 | // Remove Entity & Component 47 | moth.removeAllComponents(from: e4) // e4: [] 48 | moth.removeEntity(entityID: e4) // e4 is not invalid, its ID will be reused in next createEntity() 49 | moth.removeComponent(TagComponent.self, from: e2) // e2: [Light] 50 | 51 | // Show Bitmask Info 52 | moth.log() 53 | 54 | // Get & Has Component 55 | if moth.hasComponent(LightComponent.self, in: e2) { 56 | _ = moth.getComponent(LightComponent.self, from: e2) // put getComponent() inside hasComponent() 57 | } 58 | 59 | // View Operation 60 | let v1 = moth.view(TagComponent.self, TransformComponent.self, LightComponent.self) // v1: [e0] 61 | let v2 = moth.view(TagComponent.self, TransformComponent.self) // v2: [e0, e1] 62 | let v3 = moth.view(TagComponent.self, TransformComponent.self, excepts: LightComponent.self) // v2: [e1] 63 | 64 | print(v1) // [0] 65 | print(v2) // [0, 1] 66 | print(v3) // [1] 67 | -------------------------------------------------------------------------------- /Sources/MothECS/Moth+Component.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Moth+Component.swift 3 | // MothECS 4 | // 5 | // Created by Junhao Wang on 1/12/22. 6 | // 7 | 8 | public protocol MothComponent: AnyObject { 9 | init() 10 | } 11 | 12 | // Component Pool 13 | class MothComponentPool { 14 | // Identifiers 15 | private var componentIdentifierMap: [Int: MothComponentID] = [:] // hash -> ID 16 | private var componentIDCounter: MothComponentID = 0 17 | 18 | // Pool: ID -> [MothComponent] 19 | private var typePools: [ContiguousArray] = [] 20 | 21 | var componentTypeCount: Int { get { 22 | return Int(componentIDCounter) 23 | }} 24 | 25 | init() { 26 | 27 | } 28 | 29 | func getComponentID(_ type: T.Type) -> MothComponentID where T: MothComponent { 30 | // Each component type has its own type hash value 31 | let typeHashValue: Int = ObjectIdentifier(type.self).hashValue 32 | 33 | if let id = componentIdentifierMap[typeHashValue] { 34 | return id 35 | } else { 36 | // register new component ID 37 | let newID: MothComponentID = componentIDCounter 38 | componentIdentifierMap[typeHashValue] = newID 39 | // create component pool 40 | typePools.append(ContiguousArray(repeating: nil, count: Moth.maxEntityCount)) 41 | // update 42 | componentIDCounter += 1 43 | return newID 44 | } 45 | } 46 | 47 | func getComponent(_ type: T.Type, from entityID: MothEntityID) -> T where T: MothComponent { 48 | guard let component: T = typePools[Int(getComponentID(type))][Int(entityID)] as? T else { 49 | fatalError("Please use hasComponent() to check component existence before calling getComponent()") 50 | } 51 | 52 | return component 53 | } 54 | 55 | func getComponentList(_ type: T.Type) -> [T] where T: MothComponent { 56 | let list = typePools[Int(getComponentID(type))] 57 | return list.compactMap({ $0 as? T }) 58 | } 59 | 60 | func createComponent(_ type: T.Type, from entityID: MothEntityID) -> T where T: MothComponent { 61 | let t = T() 62 | typePools[Int(getComponentID(type))][Int(entityID)] = t 63 | return t 64 | } 65 | 66 | func assignComponent(_ component: T, to type: T.Type, from entityID: MothEntityID) where T: MothComponent { 67 | typePools[Int(getComponentID(type))][Int(entityID)] = component 68 | } 69 | 70 | func removeComponent(_ type: T.Type, from entityID: MothEntityID) -> T? where T: MothComponent { 71 | let componentID = getComponentID(type) 72 | let component = typePools[Int(componentID)][Int(entityID)] 73 | typePools[Int(componentID)][Int(entityID)] = nil 74 | return component as? T 75 | } 76 | 77 | func removeAllComponents(from entityID: MothEntityID) { 78 | for componentID in componentIdentifierMap.values { 79 | typePools[Int(componentID)][Int(entityID)] = nil 80 | } 81 | } 82 | } 83 | 84 | extension MothComponentPool: CustomStringConvertible { 85 | var description: String { 86 | "MothComponentPool: count=\(componentTypeCount), IDs=\(componentIdentifierMap.values)" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/MothECS/Moth+Entity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Moth+Entity.swift 3 | // MothECS 4 | // 5 | // Created by Junhao Wang on 1/12/22. 6 | // 7 | 8 | struct MothEntity { 9 | let entityID: MothEntityID 10 | var componentMask: MothComponentMask = MothComponentMask() 11 | 12 | init(id: MothEntityID) { 13 | entityID = id 14 | } 15 | } 16 | 17 | extension MothEntity: CustomStringConvertible { 18 | var description: String { 19 | "MothEntity: id=\(entityID), \(componentMask)" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/MothECS/Moth+Identifiers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Moth+Identifiers.swift 3 | // MothECS 4 | // 5 | // Created by Junhao Wang on 1/12/22. 6 | // 7 | 8 | public typealias MothEntityID = UInt32 9 | 10 | extension MothEntityID { 11 | public static let invalid: MothEntityID = MothEntityID.max 12 | } 13 | 14 | public typealias MothComponentID = UInt32 15 | -------------------------------------------------------------------------------- /Sources/MothECS/Moth+Mask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Moth+Mask.swift 3 | // MothECS 4 | // 5 | // Created by Junhao Wang on 1/12/22. 6 | // 7 | 8 | struct MothComponentMask { 9 | private var mask: UInt32 = 0 10 | 11 | init() { 12 | 13 | } 14 | 15 | init(indices: [MothComponentID]) { 16 | for index in indices { 17 | set(index) 18 | } 19 | } 20 | 21 | mutating func set(_ index: MothComponentID) { 22 | mask |= (1 << index) 23 | } 24 | 25 | mutating func unset(_ index: MothComponentID) { 26 | mask &= ~(1 << index) 27 | } 28 | 29 | mutating func reset() { 30 | mask = 0 31 | } 32 | 33 | func isComponentOn(_ index: MothComponentID) -> Bool { 34 | let result = (mask >> index) & 1 35 | return result != 0 36 | } 37 | 38 | func contains(_ otherMask: MothComponentMask) -> Bool { 39 | return otherMask.mask == (otherMask.mask & mask) 40 | } 41 | } 42 | 43 | extension MothComponentMask: Equatable { 44 | static func == (lhs: MothComponentMask, rhs: MothComponentMask) -> Bool { 45 | return lhs.mask == rhs.mask 46 | } 47 | } 48 | 49 | extension MothComponentMask: CustomStringConvertible { 50 | var description: String { 51 | let str = String(mask, radix: 2).pad(with: "0", 52 | toLength: Moth.maxComponentCount) 53 | return "ComponentMask: [\(String(str.reversed()))]" 54 | } 55 | } 56 | 57 | extension String { 58 | fileprivate func pad(with padding: Character, toLength length: Int) -> String { 59 | let paddingWidth = length - self.count 60 | guard 0 < paddingWidth else { return self } 61 | return String(repeating: padding, count: paddingWidth) + self 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Sources/MothECS/Moth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Moth.swift 3 | // MothECS 4 | // 5 | // Created by Junhao Wang on 1/12/22. 6 | // 7 | 8 | public class Moth { 9 | static let maxEntityCount: Int = 1000 10 | static let maxComponentCount: Int = 32 // Do not change! 32 is enough for now 11 | 12 | private var entities: [MothEntity] = [] 13 | private var freeEntities: [MothEntityID] = [] 14 | private let componentPool: MothComponentPool = MothComponentPool() 15 | 16 | public init() { 17 | 18 | } 19 | 20 | public func log() { 21 | print("MothECS:") 22 | for entity in entities { 23 | print(" -> \(entity)") 24 | } 25 | print("-- \(componentPool)") 26 | } 27 | } 28 | 29 | // MARK: - Entity Methods 30 | extension Moth { 31 | public var entityIDs: [MothEntityID] { 32 | entities.filter{ $0.entityID != .invalid }.map { $0.entityID } 33 | } 34 | 35 | public func createEntity() -> MothEntityID { 36 | guard entities.count < Self.maxEntityCount else { 37 | assertionFailure("Cannot create entity any more. Reached the limit: \(Self.maxEntityCount)") 38 | return .invalid 39 | } 40 | 41 | // Check if there is any freed ID 42 | if !freeEntities.isEmpty { 43 | let reusedID: MothEntityID = freeEntities.removeFirst() 44 | entities[Int(reusedID)] = MothEntity(id: reusedID) 45 | return reusedID 46 | } else { 47 | let entity = MothEntity(id: MothEntityID(entities.count)) 48 | entities.append(entity) 49 | return entity.entityID 50 | } 51 | } 52 | 53 | @discardableResult 54 | public func removeEntity(entityID: MothEntityID) -> Bool { 55 | guard checkEntityID(entityID) else { 56 | return false 57 | } 58 | 59 | componentPool.removeAllComponents(from: entityID) 60 | entities[Int(entityID)] = MothEntity(id: .invalid) 61 | 62 | freeEntities.append(entityID) 63 | return true 64 | } 65 | } 66 | 67 | // MARK: - Single Component Methods 68 | extension Moth { 69 | @discardableResult 70 | public func createComponent(_ type: T.Type, to entityID: MothEntityID) -> T? where T: MothComponent { 71 | guard checkEntityID(entityID) && checkComponentPoolSize() else { 72 | return nil 73 | } 74 | 75 | let componentID = componentPool.getComponentID(T.self) 76 | entities[Int(entityID)].componentMask.set(componentID) // Int is large enough 77 | 78 | let component: T = componentPool.createComponent(T.self, from: entityID) 79 | return component 80 | } 81 | 82 | @discardableResult 83 | public func assignComponent(_ component: T, to entityID: MothEntityID) -> Bool where T: MothComponent { 84 | guard checkEntityID(entityID) && checkComponentPoolSize() else { 85 | return false 86 | } 87 | 88 | let componentID = componentPool.getComponentID(T.self) 89 | entities[Int(entityID)].componentMask.set(componentID) 90 | 91 | componentPool.assignComponent(component, to: T.self, from: entityID) 92 | return true 93 | } 94 | 95 | @discardableResult 96 | public func removeComponent(_ type: T.Type, from entityID: MothEntityID) -> T? where T: MothComponent { 97 | guard checkEntityID(entityID) && checkComponentPoolSize() else { 98 | return nil 99 | } 100 | 101 | let componentID = componentPool.getComponentID(T.self) 102 | entities[Int(entityID)].componentMask.unset(componentID) 103 | return componentPool.removeComponent(type, from: entityID) 104 | } 105 | 106 | public func removeAllComponents(from entityID: MothEntityID) { 107 | guard checkEntityID(entityID) && checkComponentPoolSize() else { 108 | return 109 | } 110 | 111 | entities[Int(entityID)].componentMask.reset() 112 | componentPool.removeAllComponents(from: entityID) 113 | } 114 | 115 | @discardableResult 116 | public func getComponent(_ type: T.Type, from entityID: MothEntityID) -> T where T: MothComponent { 117 | guard checkEntityID(entityID) else { 118 | fatalError("Cannot get component from invalid entity ID (\(entityID)") 119 | } 120 | 121 | return componentPool.getComponent(type, from: entityID) 122 | } 123 | 124 | @discardableResult 125 | public func hasComponent(_ type: T.Type, in entityID: MothEntityID) -> Bool where T: MothComponent { 126 | guard checkEntityID(entityID) else { 127 | return false 128 | } 129 | 130 | let componentID = componentPool.getComponentID(type) 131 | return entities[Int(entityID)].componentMask.isComponentOn(componentID) 132 | } 133 | } 134 | 135 | // MARK: - Component View Methods 136 | extension Moth { 137 | public func view(_ type: T.Type) -> [MothEntityID] where T: MothComponent { 138 | let componentID = componentPool.getComponentID(type) 139 | let mask = MothComponentMask(indices: [componentID]) 140 | return entities.filter{ $0.componentMask.contains(mask) }.map{ $0.entityID } 141 | } 142 | 143 | public func view(_ type1: T1.Type, _ type2: T2.Type) -> [MothEntityID] where T1: MothComponent, T2: MothComponent { 144 | let componentID1 = componentPool.getComponentID(type1) 145 | let componentID2 = componentPool.getComponentID(type2) 146 | let mask = MothComponentMask(indices: [componentID1, componentID2]) 147 | return entities.filter{ $0.componentMask.contains(mask) }.map{ $0.entityID } 148 | } 149 | 150 | public func view(_ type1: T1.Type, _ type2: T2.Type, _ type3: T3.Type) 151 | -> [MothEntityID] where T1: MothComponent, T2: MothComponent, T3: MothComponent { 152 | let componentID1 = componentPool.getComponentID(type1) 153 | let componentID2 = componentPool.getComponentID(type2) 154 | let componentID3 = componentPool.getComponentID(type3) 155 | let mask = MothComponentMask(indices: [componentID1, componentID2, componentID3]) 156 | return entities.filter{ $0.componentMask.contains(mask) }.map{ $0.entityID } 157 | } 158 | 159 | // With Exceptions 160 | public func view(excepts type: T.Type) -> [MothEntityID] where T: MothComponent { 161 | let componentID = componentPool.getComponentID(type) 162 | let mask = MothComponentMask(indices: [componentID]) 163 | return entities.filter{ !$0.componentMask.contains(mask) }.map{ $0.entityID } 164 | } 165 | 166 | public func view(_ type: T1.Type, excepts exceptType: T2.Type) -> [MothEntityID] where T1: MothComponent, T2: MothComponent { 167 | let componentID = componentPool.getComponentID(type) 168 | let exceptComponentID = componentPool.getComponentID(exceptType) 169 | let mask = MothComponentMask(indices: [componentID]) 170 | let exceptMask = MothComponentMask(indices: [exceptComponentID]) 171 | return entities.filter{ $0.componentMask.contains(mask) && !$0.componentMask.contains(exceptMask) }.map{ $0.entityID } 172 | } 173 | 174 | public func view(_ type1: T1.Type, _ type2: T2.Type, excepts exceptType: T3.Type) 175 | -> [MothEntityID] where T1: MothComponent, T2: MothComponent, T3: MothComponent { 176 | let componentID1 = componentPool.getComponentID(type1) 177 | let componentID2 = componentPool.getComponentID(type2) 178 | let exceptComponentID = componentPool.getComponentID(exceptType) 179 | let mask = MothComponentMask(indices: [componentID1, componentID2]) 180 | let exceptMask = MothComponentMask(indices: [exceptComponentID]) 181 | return entities.filter{ $0.componentMask.contains(mask) && !$0.componentMask.contains(exceptMask) }.map{ $0.entityID } 182 | } 183 | 184 | public func list(_ type: T.Type) -> [T] where T: MothComponent { // might be slow, use view() 185 | return componentPool.getComponentList(type) 186 | } 187 | } 188 | 189 | // MARK: - Check Helper Functions 190 | extension Moth { 191 | private func checkEntityID(_ entityID: MothEntityID) -> Bool { 192 | guard entityID < entities.count else { 193 | assertionFailure("Cannot assign component to invalid entity ID (\(entityID)!") 194 | return false 195 | } 196 | 197 | guard entities[Int(entityID)].entityID != .invalid else { 198 | assertionFailure("The entity (\(entityID) has been removed!") 199 | return false 200 | } 201 | 202 | return true 203 | } 204 | 205 | private func checkComponentPoolSize() -> Bool { 206 | guard componentPool.componentTypeCount < Self.maxComponentCount else { 207 | assertionFailure("Cannot create component. Currently only supports \(Self.maxComponentCount) components!") 208 | return false 209 | } 210 | return true 211 | } 212 | } 213 | --------------------------------------------------------------------------------