├── .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 | [](https://github.com/forkercat/MothECS/actions/workflows/ci-macos.yml)
4 | [](https://github.com/forkercat/MothECS/actions/workflows/ci-linux.yml)
5 | [](https://github.com/forkercat/MothECS/actions/workflows/ci-windows.yml)
6 | [](LICENSE)
7 |
8 | [](https://swiftpackageindex.com/forkercat/MothECS)
9 | [](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 |
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 |
--------------------------------------------------------------------------------