Skip to content

Commit

Permalink
Merge branch 'release/0.16.0' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
ctreffs committed Aug 22, 2020
2 parents 7613cb2 + dd16ccc commit 36e8c79
Show file tree
Hide file tree
Showing 16 changed files with 186 additions and 107 deletions.
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# Fireblade ECS (Entity-Component System)
[![github CI](https://github.com/fireblade-engine/ecs/workflows/CI/badge.svg)](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
[![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
[![swift version](https://img.shields.io/badge/swift-5.1+-brightgreen.svg)](https://swift.org)
[![platforms](https://img.shields.io/badge/platforms-%20macOS%20|%20iOS%20|%20tvOS%20|%20watchOS-brightgreen.svg)](#)
[![platforms](https://img.shields.io/badge/platforms-linux-brightgreen.svg)](#)
[![platforms](https://img.shields.io/badge/platforms-WebAssembly-brightgreen.svg)](https://github.com/swiftwasm/swift#swiftwasm)
[![github CI](https://github.com/fireblade-engine/ecs/workflows/CI/badge.svg)](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
[![codecov](https://codecov.io/gh/fireblade-engine/ecs/branch/master/graph/badge.svg)](https://codecov.io/gh/fireblade-engine/ecs)
[![documentation](https://github.com/fireblade-engine/ecs/workflows/Documentation/badge.svg)](https://github.com/fireblade-engine/ecs/wiki)
[![documentation](https://github.com/fireblade-engine/ecs/workflows/Documentation/badge.svg)](https://github.com/fireblade-engine/ecs/wiki)
[![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)
[![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)
[![platform-webassembly](https://img.shields.io/badge/Platform-WebAssembly-blue.svg)](https://github.com/swiftwasm/swift#swiftwasm)

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).

Expand Down Expand Up @@ -36,7 +35,7 @@ import PackageDescription
let package = Package(
name: "YourPackageName",
dependencies: [
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.15.4")
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.16.0")
],
targets: [
.target(
Expand Down
12 changes: 4 additions & 8 deletions Sources/FirebladeECS/ComponentIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,18 @@

/// Identifies a component by it's meta type
public struct ComponentIdentifier {
@usableFromInline
typealias Hash = Int
@usableFromInline
typealias StableId = UInt64

@usableFromInline let hash: Hash
public typealias Identifier = Int
public let id: Identifier
}

extension ComponentIdentifier {
@usableFromInline
init<C>(_ componentType: C.Type) where C: Component {
self.hash = Self.makeRuntimeHash(componentType)
self.id = Self.makeRuntimeHash(componentType)
}

/// object identifier hash (only stable during runtime) - arbitrary hash is ok.
internal static func makeRuntimeHash<C>(_ componentType: C.Type) -> Hash where C: Component {
internal static func makeRuntimeHash<C>(_ componentType: C.Type) -> Identifier where C: Component {
ObjectIdentifier(componentType).hashValue
}
}
Expand Down
71 changes: 46 additions & 25 deletions Sources/FirebladeECS/EntityIdentifierGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@
// Created by Christian Treffs on 26.06.20.
//

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

/// Provides the next unused entity identifier.
///
/// The provided entity identifier is at least unique during runtime.
/// The provided entity identifier must be unique during runtime.
func nextId() -> EntityIdentifier

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

/// A default entity identifier generator implementation.
public typealias DefaultEntityIdGenerator = LinearIncrementingEntityIdGenerator

/// **Linear incrementing entity id generator**
///
/// Provides entity ids starting at `0` incrementing until `UInt32.max`.
public struct DefaultEntityIdGenerator: EntityIdentifierGenerator {
/// This entity id generator creates linearly incrementing entity ids
/// unless an entity is marked as unused then the marked id is returned next in a FIFO order.
///
/// Furthermore it respects order of entity ids on initialization, meaning the provided ids on initialization will be provided in order
/// until all are in use. After that the free entities start at the lowest available id increasing linearly skipping already in-use entity ids.
public struct LinearIncrementingEntityIdGenerator: EntityIdentifierGenerator {
@usableFromInline
final class Storage {
@usableFromInline var stack: [UInt32]
@usableFromInline var stack: [EntityIdentifier.Identifier]
@usableFromInline var count: Int { stack.count }

@usableFromInline
init(inUse entityIds: [EntityIdentifier]) {
stack = entityIds.reversed().map { UInt32($0.id) }
init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier {
let initialInUse: [EntityIdentifier.Identifier] = initialEntityIds.map { $0.id }
let maxInUseValue = initialInUse.max() ?? 0
let inUseSet = Set(initialInUse) // a set of all eIds in use
let allSet = Set(0...maxInUseValue) // all eIds from 0 to including maxInUseValue
let freeSet = allSet.subtracting(inUseSet) // all "holes" / unused / free eIds
let initialFree = Array(freeSet).sorted().reversed() // order them to provide them linear increasing after all initially used are provided.
stack = initialFree + initialInUse
}

@usableFromInline
init() {
stack = [0]
}

@usableFromInline
func nextId() -> EntityIdentifier {
if stack.count == 1 {
defer { stack[0] += 1 }
return EntityIdentifier(stack[0])
} else {
guard stack.count == 1 else {
return EntityIdentifier(stack.removeLast())
}
defer { stack[0] += 1 }
return EntityIdentifier(stack[0])
}

@usableFromInline
func markUnused(entityId: EntityIdentifier) {
stack.append(UInt32(entityId.id))
stack.append(entityId.id)
}
}

@usableFromInline let storage: Storage

@usableFromInline var count: Int { storage.count }

@inlinable
public init() {
self.init(inUse: [EntityIdentifier(0)])
public init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier {
self.storage = Storage(startProviding: initialEntityIds)
}

@inlinable
public init(inUse entityIds: [EntityIdentifier]) {
self.storage = Storage(inUse: entityIds)
public init() {
self.storage = Storage()
}

@inlinable
@inline(__always)
public func nextId() -> EntityIdentifier {
storage.nextId()
}

@inlinable
@inline(__always)
public func markUnused(entityId: EntityIdentifier) {
storage.markUnused(entityId: entityId)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/FirebladeECS/Family.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ extension Family {
guard let entityId = memberIdsIterator.next() else {
return nil
}
return nexus.get(unsafeEntity: entityId)
return Entity(nexus: nexus, id: entityId)
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions Sources/FirebladeECS/Generated/Family.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct Requires1<Comp1>: FamilyRequirementsManaging where Comp1: Componen
}

public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1) {
let entity: Entity = nexus.get(unsafeEntity: entityId)
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
return (entity, comp1)
}
Expand Down Expand Up @@ -122,7 +122,7 @@ public struct Requires2<Comp1, Comp2>: FamilyRequirementsManaging where Comp1: C
}

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

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

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

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

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

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

public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8) {
let entity: Entity = nexus.get(unsafeEntity: entityId)
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
Expand Down
9 changes: 0 additions & 9 deletions Sources/FirebladeECS/Hashing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,6 @@ public func hash(combine seed: Int, _ value: Int) -> Int {
return Int(bitPattern: uSeed)
}

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

/// Calculates the combined hash value of the elements. This implementation is based on boost::hash_range.
/// Is sensitive to the order of the elements.
/// - Parameter hashValues: sequence of hash values to combine.
Expand Down
23 changes: 10 additions & 13 deletions Sources/FirebladeECS/Nexus+Entity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension Nexus {
@discardableResult
public func createEntity() -> Entity {
let entityId: EntityIdentifier = entityIdGenerator.nextId()
entityStorage.insert(entityId, at: entityId.id)
componentIdsByEntity[entityId] = []
delegate?.nexusEvent(EntityCreated(entityId: entityId))
return Entity(nexus: self, id: entityId)
}
Expand All @@ -30,22 +30,15 @@ extension Nexus {

/// Number of entities in nexus.
public var numEntities: Int {
entityStorage.count
componentIdsByEntity.keys.count
}

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

public func get(entity entityId: EntityIdentifier) -> Entity? {
guard let id = entityStorage.get(at: entityId.id) else {
return nil
}
return Entity(nexus: self, id: id)
}

public func get(unsafeEntity entityId: EntityIdentifier) -> Entity {
Entity(nexus: self, id: entityStorage.get(unsafeAt: entityId.id))
public func entity(from entityId: EntityIdentifier) -> Entity {
Entity(nexus: self, id: entityId)
}

@discardableResult
Expand All @@ -55,7 +48,7 @@ extension Nexus {

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

if let index = componentIdsByEntity.index(forKey: entityId) {
componentIdsByEntity.remove(at: index)
}

entityIdGenerator.markUnused(entityId: entityId)

delegate?.nexusEvent(EntityDestroyed(entityId: entityId))
Expand Down
16 changes: 2 additions & 14 deletions Sources/FirebladeECS/Nexus+Internal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,7 @@ extension Nexus {
}

func assign(_ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) {
if componentIdsByEntity[entityId] == nil {
componentIdsByEntity[entityId] = Set<ComponentIdentifier>(arrayLiteral: componentId)
} else {
componentIdsByEntity[entityId]?.insert(componentId)
}
}

func assign(_ componentIds: Set<ComponentIdentifier>, _ entityId: EntityIdentifier) {
if componentIdsByEntity[entityId] == nil {
componentIdsByEntity[entityId] = componentIds
} else {
componentIdsByEntity[entityId]?.formUnion(componentIds)
}
componentIdsByEntity[entityId]!.insert(componentId)
}

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

func update(familyMembership traits: FamilyTraitSet) {
// FIXME: iterating all entities is costly for many entities
var iter = entityStorage.makeIterator()
var iter = componentIdsByEntity.keys.makeIterator()
while let entityId = iter.next() {
update(membership: traits, for: entityId)
}
Expand Down
Loading

0 comments on commit 36e8c79

Please sign in to comment.