Skip to content

Commit 36e8c79

Browse files
committed
Merge branch 'release/0.16.0' into master
2 parents 7613cb2 + dd16ccc commit 36e8c79

16 files changed

+186
-107
lines changed

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
# Fireblade ECS (Entity-Component System)
2-
[![github CI](https://github.com/fireblade-engine/ecs/workflows/CI/badge.svg)](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
32
[![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
4-
[![swift version](https://img.shields.io/badge/swift-5.1+-brightgreen.svg)](https://swift.org)
5-
[![platforms](https://img.shields.io/badge/platforms-%20macOS%20|%20iOS%20|%20tvOS%20|%20watchOS-brightgreen.svg)](#)
6-
[![platforms](https://img.shields.io/badge/platforms-linux-brightgreen.svg)](#)
7-
[![platforms](https://img.shields.io/badge/platforms-WebAssembly-brightgreen.svg)](https://github.com/swiftwasm/swift#swiftwasm)
3+
[![github CI](https://github.com/fireblade-engine/ecs/workflows/CI/badge.svg)](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
84
[![codecov](https://codecov.io/gh/fireblade-engine/ecs/branch/master/graph/badge.svg)](https://codecov.io/gh/fireblade-engine/ecs)
9-
[![documentation](https://github.com/fireblade-engine/ecs/workflows/Documentation/badge.svg)](https://github.com/fireblade-engine/ecs/wiki)
5+
[![documentation](https://github.com/fireblade-engine/ecs/workflows/Documentation/badge.svg)](https://github.com/fireblade-engine/ecs/wiki)
6+
[![spi-swift-versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffireblade-engine%2Fecs%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/fireblade-engine/ecs)
7+
[![spi-swift-platforms](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffireblade-engine%2Fecs%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/fireblade-engine/ecs)
8+
[![platform-webassembly](https://img.shields.io/badge/Platform-WebAssembly-blue.svg)](https://github.com/swiftwasm/swift#swiftwasm)
109

1110
This is a **dependency free**, **lightweight**, **fast** and **easy to use** [Entity-Component System](https://en.wikipedia.org/wiki/Entity_component_system) implementation in Swift. It is developed and maintained as part of the [Fireblade Game Engine project](https://github.com/fireblade-engine).
1211

@@ -36,7 +35,7 @@ import PackageDescription
3635
let package = Package(
3736
name: "YourPackageName",
3837
dependencies: [
39-
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.15.4")
38+
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.16.0")
4039
],
4140
targets: [
4241
.target(

Sources/FirebladeECS/ComponentIdentifier.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,18 @@
77

88
/// Identifies a component by it's meta type
99
public struct ComponentIdentifier {
10-
@usableFromInline
11-
typealias Hash = Int
12-
@usableFromInline
13-
typealias StableId = UInt64
14-
15-
@usableFromInline let hash: Hash
10+
public typealias Identifier = Int
11+
public let id: Identifier
1612
}
1713

1814
extension ComponentIdentifier {
1915
@usableFromInline
2016
init<C>(_ componentType: C.Type) where C: Component {
21-
self.hash = Self.makeRuntimeHash(componentType)
17+
self.id = Self.makeRuntimeHash(componentType)
2218
}
2319

2420
/// object identifier hash (only stable during runtime) - arbitrary hash is ok.
25-
internal static func makeRuntimeHash<C>(_ componentType: C.Type) -> Hash where C: Component {
21+
internal static func makeRuntimeHash<C>(_ componentType: C.Type) -> Identifier where C: Component {
2622
ObjectIdentifier(componentType).hashValue
2723
}
2824
}

Sources/FirebladeECS/EntityIdentifierGenerator.swift

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@
55
// Created by Christian Treffs on 26.06.20.
66
//
77

8-
/// An entity identifier generator provides new entity
9-
/// identifiers on entity creation.
10-
/// It also allows entity ids to be marked for re-use.
11-
/// Entity identifiers must be unique.
8+
/// **Entity Identifier Generator**
9+
///
10+
/// An entity identifier generator provides new entity identifiers on entity creation.
11+
/// It also allows entity ids to be marked as unused (to be re-usable).
12+
///
13+
/// You should strive to keep entity ids tightly packed around `EntityIdentifier.Identifier.min` since it has an influence on the underlying memory layout.
1214
public protocol EntityIdentifierGenerator {
13-
/// Initialize the generator with entity ids already in use.
14-
/// - Parameter entityIds: The entity ids already in use. Default should be an empty array.
15-
init(inUse entityIds: [EntityIdentifier])
15+
/// Initialize the generator providing entity ids to begin with when creating new entities.
16+
///
17+
/// Entity ids provided should be passed to `nextId()` in last out order up until the collection is empty.
18+
/// The default is an empty collection.
19+
/// - Parameter initialEntityIds: The entity ids to start providing up until the collection is empty (in last out order).
20+
init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier
1621

1722
/// Provides the next unused entity identifier.
1823
///
19-
/// The provided entity identifier is at least unique during runtime.
24+
/// The provided entity identifier must be unique during runtime.
2025
func nextId() -> EntityIdentifier
2126

2227
/// Marks the given entity identifier as free and ready for re-use.
@@ -27,55 +32,71 @@ public protocol EntityIdentifierGenerator {
2732
}
2833

2934
/// A default entity identifier generator implementation.
35+
public typealias DefaultEntityIdGenerator = LinearIncrementingEntityIdGenerator
36+
37+
/// **Linear incrementing entity id generator**
3038
///
31-
/// Provides entity ids starting at `0` incrementing until `UInt32.max`.
32-
public struct DefaultEntityIdGenerator: EntityIdentifierGenerator {
39+
/// This entity id generator creates linearly incrementing entity ids
40+
/// unless an entity is marked as unused then the marked id is returned next in a FIFO order.
41+
///
42+
/// Furthermore it respects order of entity ids on initialization, meaning the provided ids on initialization will be provided in order
43+
/// until all are in use. After that the free entities start at the lowest available id increasing linearly skipping already in-use entity ids.
44+
public struct LinearIncrementingEntityIdGenerator: EntityIdentifierGenerator {
3345
@usableFromInline
3446
final class Storage {
35-
@usableFromInline var stack: [UInt32]
47+
@usableFromInline var stack: [EntityIdentifier.Identifier]
3648
@usableFromInline var count: Int { stack.count }
3749

3850
@usableFromInline
39-
init(inUse entityIds: [EntityIdentifier]) {
40-
stack = entityIds.reversed().map { UInt32($0.id) }
51+
init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier {
52+
let initialInUse: [EntityIdentifier.Identifier] = initialEntityIds.map { $0.id }
53+
let maxInUseValue = initialInUse.max() ?? 0
54+
let inUseSet = Set(initialInUse) // a set of all eIds in use
55+
let allSet = Set(0...maxInUseValue) // all eIds from 0 to including maxInUseValue
56+
let freeSet = allSet.subtracting(inUseSet) // all "holes" / unused / free eIds
57+
let initialFree = Array(freeSet).sorted().reversed() // order them to provide them linear increasing after all initially used are provided.
58+
stack = initialFree + initialInUse
59+
}
60+
61+
@usableFromInline
62+
init() {
63+
stack = [0]
4164
}
4265

4366
@usableFromInline
4467
func nextId() -> EntityIdentifier {
45-
if stack.count == 1 {
46-
defer { stack[0] += 1 }
47-
return EntityIdentifier(stack[0])
48-
} else {
68+
guard stack.count == 1 else {
4969
return EntityIdentifier(stack.removeLast())
5070
}
71+
defer { stack[0] += 1 }
72+
return EntityIdentifier(stack[0])
5173
}
5274

5375
@usableFromInline
5476
func markUnused(entityId: EntityIdentifier) {
55-
stack.append(UInt32(entityId.id))
77+
stack.append(entityId.id)
5678
}
5779
}
5880

5981
@usableFromInline let storage: Storage
60-
6182
@usableFromInline var count: Int { storage.count }
6283

6384
@inlinable
64-
public init() {
65-
self.init(inUse: [EntityIdentifier(0)])
85+
public init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier {
86+
self.storage = Storage(startProviding: initialEntityIds)
6687
}
6788

6889
@inlinable
69-
public init(inUse entityIds: [EntityIdentifier]) {
70-
self.storage = Storage(inUse: entityIds)
90+
public init() {
91+
self.storage = Storage()
7192
}
7293

73-
@inlinable
94+
@inline(__always)
7495
public func nextId() -> EntityIdentifier {
7596
storage.nextId()
7697
}
7798

78-
@inlinable
99+
@inline(__always)
79100
public func markUnused(entityId: EntityIdentifier) {
80101
storage.markUnused(entityId: entityId)
81102
}

Sources/FirebladeECS/Family.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ extension Family {
114114
guard let entityId = memberIdsIterator.next() else {
115115
return nil
116116
}
117-
return nexus.get(unsafeEntity: entityId)
117+
return Entity(nexus: nexus, id: entityId)
118118
}
119119
}
120120
}

Sources/FirebladeECS/Generated/Family.generated.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public struct Requires1<Comp1>: FamilyRequirementsManaging where Comp1: Componen
2828
}
2929

3030
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1) {
31-
let entity: Entity = nexus.get(unsafeEntity: entityId)
31+
let entity = Entity(nexus: nexus, id: entityId)
3232
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
3333
return (entity, comp1)
3434
}
@@ -122,7 +122,7 @@ public struct Requires2<Comp1, Comp2>: FamilyRequirementsManaging where Comp1: C
122122
}
123123

124124
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2) {
125-
let entity: Entity = nexus.get(unsafeEntity: entityId)
125+
let entity = Entity(nexus: nexus, id: entityId)
126126
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
127127
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
128128
return (entity, comp1, comp2)
@@ -222,7 +222,7 @@ public struct Requires3<Comp1, Comp2, Comp3>: FamilyRequirementsManaging where C
222222
}
223223

224224
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3) {
225-
let entity: Entity = nexus.get(unsafeEntity: entityId)
225+
let entity = Entity(nexus: nexus, id: entityId)
226226
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
227227
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
228228
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
@@ -328,7 +328,7 @@ public struct Requires4<Comp1, Comp2, Comp3, Comp4>: FamilyRequirementsManaging
328328
}
329329

330330
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4) {
331-
let entity: Entity = nexus.get(unsafeEntity: entityId)
331+
let entity = Entity(nexus: nexus, id: entityId)
332332
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
333333
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
334334
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
@@ -440,7 +440,7 @@ public struct Requires5<Comp1, Comp2, Comp3, Comp4, Comp5>: FamilyRequirementsMa
440440
}
441441

442442
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5) {
443-
let entity: Entity = nexus.get(unsafeEntity: entityId)
443+
let entity = Entity(nexus: nexus, id: entityId)
444444
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
445445
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
446446
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
@@ -558,7 +558,7 @@ public struct Requires6<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6>: FamilyRequire
558558
}
559559

560560
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6) {
561-
let entity: Entity = nexus.get(unsafeEntity: entityId)
561+
let entity = Entity(nexus: nexus, id: entityId)
562562
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
563563
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
564564
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
@@ -682,7 +682,7 @@ public struct Requires7<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7>: Family
682682
}
683683

684684
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7) {
685-
let entity: Entity = nexus.get(unsafeEntity: entityId)
685+
let entity = Entity(nexus: nexus, id: entityId)
686686
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
687687
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
688688
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
@@ -812,7 +812,7 @@ public struct Requires8<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8>:
812812
}
813813

814814
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8) {
815-
let entity: Entity = nexus.get(unsafeEntity: entityId)
815+
let entity = Entity(nexus: nexus, id: entityId)
816816
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
817817
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
818818
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)

Sources/FirebladeECS/Hashing.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,6 @@ public func hash(combine seed: Int, _ value: Int) -> Int {
4545
return Int(bitPattern: uSeed)
4646
}
4747

48-
/// Calculates the combined hash value of the elements. This implementation is based on boost::hash_range.
49-
/// Is sensitive to the order of the elements.
50-
/// - Parameter hashValues: sequence of hash values to combine.
51-
/// - Returns: combined hash value.
52-
public func hash<S: Sequence>(combine hashValues: S) -> Int where S.Element == Int {
53-
/// http://www.boost.org/doc/libs/1_65_1/doc/html/hash/reference.html#boost.hash_range_idp517643120
54-
hashValues.reduce(0) { hash(combine: $0, $1) }
55-
}
56-
5748
/// Calculates the combined hash value of the elements. This implementation is based on boost::hash_range.
5849
/// Is sensitive to the order of the elements.
5950
/// - Parameter hashValues: sequence of hash values to combine.

Sources/FirebladeECS/Nexus+Entity.swift

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension Nexus {
99
@discardableResult
1010
public func createEntity() -> Entity {
1111
let entityId: EntityIdentifier = entityIdGenerator.nextId()
12-
entityStorage.insert(entityId, at: entityId.id)
12+
componentIdsByEntity[entityId] = []
1313
delegate?.nexusEvent(EntityCreated(entityId: entityId))
1414
return Entity(nexus: self, id: entityId)
1515
}
@@ -30,22 +30,15 @@ extension Nexus {
3030

3131
/// Number of entities in nexus.
3232
public var numEntities: Int {
33-
entityStorage.count
33+
componentIdsByEntity.keys.count
3434
}
3535

3636
public func exists(entity entityId: EntityIdentifier) -> Bool {
37-
entityStorage.contains(entityId.id)
37+
componentIdsByEntity.keys.contains(entityId)
3838
}
3939

40-
public func get(entity entityId: EntityIdentifier) -> Entity? {
41-
guard let id = entityStorage.get(at: entityId.id) else {
42-
return nil
43-
}
44-
return Entity(nexus: self, id: id)
45-
}
46-
47-
public func get(unsafeEntity entityId: EntityIdentifier) -> Entity {
48-
Entity(nexus: self, id: entityStorage.get(unsafeAt: entityId.id))
40+
public func entity(from entityId: EntityIdentifier) -> Entity {
41+
Entity(nexus: self, id: entityId)
4942
}
5043

5144
@discardableResult
@@ -55,7 +48,7 @@ extension Nexus {
5548

5649
@discardableResult
5750
public func destroy(entityId: EntityIdentifier) -> Bool {
58-
guard entityStorage.remove(at: entityId.id) != nil else {
51+
guard componentIdsByEntity.keys.contains(entityId) else {
5952
delegate?.nexusNonFatalError("EntityRemove failure: no entity \(entityId) to remove")
6053
return false
6154
}
@@ -64,6 +57,10 @@ extension Nexus {
6457
update(familyMembership: entityId)
6558
}
6659

60+
if let index = componentIdsByEntity.index(forKey: entityId) {
61+
componentIdsByEntity.remove(at: index)
62+
}
63+
6764
entityIdGenerator.markUnused(entityId: entityId)
6865

6966
delegate?.nexusEvent(EntityDestroyed(entityId: entityId))

Sources/FirebladeECS/Nexus+Internal.swift

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,7 @@ extension Nexus {
5656
}
5757

5858
func assign(_ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) {
59-
if componentIdsByEntity[entityId] == nil {
60-
componentIdsByEntity[entityId] = Set<ComponentIdentifier>(arrayLiteral: componentId)
61-
} else {
62-
componentIdsByEntity[entityId]?.insert(componentId)
63-
}
64-
}
65-
66-
func assign(_ componentIds: Set<ComponentIdentifier>, _ entityId: EntityIdentifier) {
67-
if componentIdsByEntity[entityId] == nil {
68-
componentIdsByEntity[entityId] = componentIds
69-
} else {
70-
componentIdsByEntity[entityId]?.formUnion(componentIds)
71-
}
59+
componentIdsByEntity[entityId]!.insert(componentId)
7260
}
7361

7462
func update(familyMembership entityId: EntityIdentifier) {
@@ -92,7 +80,7 @@ extension Nexus {
9280

9381
func update(familyMembership traits: FamilyTraitSet) {
9482
// FIXME: iterating all entities is costly for many entities
95-
var iter = entityStorage.makeIterator()
83+
var iter = componentIdsByEntity.keys.makeIterator()
9684
while let entityId = iter.next() {
9785
update(membership: traits, for: entityId)
9886
}

0 commit comments

Comments
 (0)