Merge branch 'feature/codable' into 'develop'

v0.11 | Codable

See merge request fireblade/ecs!5
This commit is contained in:
Christian Treffs 2020-04-30 18:12:13 +00:00
commit 0af7cc0bfe
49 changed files with 621 additions and 469 deletions

1
.gitignore vendored
View File

@ -46,7 +46,6 @@ fastlane/test_output
Gemfile* Gemfile*
Icon Icon
Network Trash Folder Network Trash Folder
Package.resolved
Packages Packages
playground.xcworkspace playground.xcworkspace
Temporary Items Temporary Items

View File

@ -1,4 +1,4 @@
image: swift:5.0 image: swift:5.2.2
#before_script: #before_script:
#- eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" #- eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"

View File

@ -1 +1 @@
5.0.3 5.2.2

View File

@ -57,7 +57,7 @@ opt_in_rules:
- multiline_arguments - multiline_arguments
- multiline_function_chains - multiline_function_chains
- multiline_parameters - multiline_parameters
- multiline_parameters_brackets #- multiline_parameters_brackets
- nimble_operator - nimble_operator
- no_extension_access_modifier - no_extension_access_modifier
- number_separator - number_separator

View File

@ -11,14 +11,4 @@ script:
- swift package reset - swift package reset
- swift build - swift build
- swift test - swift test
env:
- BADGE=linux
- BADGE=osx
# hack to get some OS-specific badges
# see: https://github.com/travis-ci/travis-ci/issues/9579
matrix:
exclude:
- os: linux
env: BADGE=osx
- os: osx
env: BADGE=linux

View File

@ -1,7 +1,23 @@
# Version 1.0.0
UNAME_S := $(shell uname -s)
# Lint
lint: lint:
swiftlint autocorrect --format swiftlint autocorrect --format
swiftlint lint --quiet swiftlint lint --quiet
lintErrorOnly:
@swiftlint autocorrect --format --quiet
@swiftlint lint --quiet | grep error
# Git
precommit: lint genLinuxTests
submodule:
git submodule init
git submodule update --recursive
# Tests
genLinuxTests: genLinuxTests:
swift test --generate-linuxmain swift test --generate-linuxmain
swiftlint autocorrect --format --path Tests/ swiftlint autocorrect --format --path Tests/
@ -9,6 +25,21 @@ genLinuxTests:
test: genLinuxTests test: genLinuxTests
swift test swift test
# Package
latest:
swift package update
resolve:
swift package resolve
# Xcode
genXcode:
swift package generate-xcodeproj --enable-code-coverage --skip-extra-files
genXcodeOpen: genXcode
open *.xcodeproj
# Clean
clean: clean:
swift package reset swift package reset
rm -rdf .swiftpm/xcode rm -rdf .swiftpm/xcode
@ -19,19 +50,7 @@ clean:
cleanArtifacts: cleanArtifacts:
swift package clean swift package clean
genXcode: # Test links in README
swift package generate-xcodeproj --enable-code-coverage --skip-extra-files # requires <https://github.com/tcort/markdown-link-check>
latest:
swift package update
resolve:
swift package resolve
genXcodeOpen: genXcode
open *.xcodeproj
precommit: lint genLinuxTests
testReadme: testReadme:
markdown-link-check -p -v ./README.md markdown-link-check -p -v ./README.md

View File

@ -1,7 +1,7 @@
# Fireblade ECS (Entity-Component System) # Fireblade ECS (Entity-Component System)
[![Build Status](https://travis-ci.com/fireblade-engine/ecs.svg?branch=master)](https://travis-ci.com/fireblade-engine/ecs) [![Build Status](https://travis-ci.com/fireblade-engine/ecs.svg?branch=master)](https://travis-ci.com/fireblade-engine/ecs)
[![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
[![swift version](https://img.shields.io/badge/swift-5.0+-brightgreen.svg)](https://swift.org/download) [![swift version](https://img.shields.io/badge/swift-5+-brightgreen.svg)](https://swift.org/download)
[![platforms](https://img.shields.io/badge/platforms-%20macOS%20|%20iOS%20|%20tvOS%20|%20watchOS-brightgreen.svg)](#) [![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-linux-brightgreen.svg)](#)
@ -21,7 +21,7 @@ These instructions will get you a copy of the project up and running on your loc
### 💻 Installing ### 💻 Installing
Fireblade ECS is available for all platforms that support [Swift 5.0](https://swift.org/) and higher and the [Swift Package Manager (SPM)](https://github.com/apple/swift-package-manager). Fireblade ECS is available for all platforms that support [Swift 5](https://swift.org/) and higher and the [Swift Package Manager (SPM)](https://github.com/apple/swift-package-manager).
Extend the following lines in your `Package.swift` file or use it to create a new project. Extend the following lines in your `Package.swift` file or use it to create a new project.

View File

@ -1,37 +0,0 @@
//
// Component+Access.swift
//
//
// Created by Christian Treffs on 25.06.19.
//
#if swift(>=5.1)
@dynamicMemberLookup
public struct ReadableOnly<Comp> where Comp: Component {
@usableFromInline let component: Comp
public init(_ component: Comp) {
self.component = component
}
@inlinable
public subscript<C>(dynamicMember keyPath: KeyPath<Comp, C>) -> C {
return component[keyPath: keyPath]
}
}
@dynamicMemberLookup
public struct Writable<Comp> where Comp: Component {
@usableFromInline let component: Comp
public init(_ component: Comp) {
self.component = component
}
@inlinable
public subscript<C>(dynamicMember keyPath: ReferenceWritableKeyPath<Comp, C>) -> C {
nonmutating get { return component[keyPath: keyPath] }
nonmutating set { component[keyPath: keyPath] = newValue }
}
}
#endif

View File

@ -7,14 +7,17 @@
/// **Component** /// **Component**
/// ///
/// A component represents the raw data for one aspect of the object, /// A component represents the raw data for one aspect of an entity.
/// and how it interacts with the world. public protocol Component: AnyObject {
public protocol Component: class { /// Unique, immutable identifier of this component type.
static var identifier: ComponentIdentifier { get } static var identifier: ComponentIdentifier { get }
/// Unique, immutable identifier of this component type.
var identifier: ComponentIdentifier { get } var identifier: ComponentIdentifier { get }
} }
extension Component { extension Component {
public static var identifier: ComponentIdentifier { return ComponentIdentifier(Self.self) } public static var identifier: ComponentIdentifier { ComponentIdentifier(Self.self) }
@inlinable public var identifier: ComponentIdentifier { return Self.identifier } @inline(__always)
public var identifier: ComponentIdentifier { Self.identifier }
} }

View File

@ -6,11 +6,19 @@
// //
/// Identifies a component by it's meta type /// Identifies a component by it's meta type
public struct ComponentIdentifier: Identifiable { public struct ComponentIdentifier {
public let id: ObjectIdentifier @usableFromInline
typealias Hash = Int
@usableFromInline
typealias StableId = UInt
init<T>(_ type: T.Type) where T: Component { @usableFromInline let hash: Hash
self.id = ObjectIdentifier(type) }
extension ComponentIdentifier {
@usableFromInline
init<C>(_ componentType: C.Type) where C: Component {
self.hash = Nexus.makeOrGetComponentId(componentType)
} }
} }

View File

@ -8,12 +8,12 @@
extension Entity { extension Entity {
@inlinable @inlinable
public func get<C>() -> C? where C: Component { public func get<C>() -> C? where C: Component {
return nexus.get(for: identifier) nexus.get(for: identifier)
} }
@inlinable @inlinable
public func get<A>(component compType: A.Type = A.self) -> A? where A: Component { public func get<A>(component compType: A.Type = A.self) -> A? where A: Component {
return nexus.get(for: identifier) nexus.get(for: identifier)
} }
@inlinable @inlinable

View File

@ -24,24 +24,24 @@ public struct Entity {
/// Returns the number of components for this entity. /// Returns the number of components for this entity.
public var numComponents: Int { public var numComponents: Int {
return nexus.count(components: identifier) nexus.count(components: identifier)
} }
/// Checks if a component with given type is assigned to this entity. /// Checks if a component with given type is assigned to this entity.
/// - Parameter type: the component type. /// - Parameter type: the component type.
public func has<C>(_ type: C.Type) -> Bool where C: Component { public func has<C>(_ type: C.Type) -> Bool where C: Component {
return has(type.identifier) has(type.identifier)
} }
/// Checks if a component with a given component identifier is assigned to this entity. /// Checks if a component with a given component identifier is assigned to this entity.
/// - Parameter compId: the component identifier. /// - Parameter compId: the component identifier.
public func has(_ compId: ComponentIdentifier) -> Bool { public func has(_ compId: ComponentIdentifier) -> Bool {
return nexus.has(componentId: compId, entityId: identifier) nexus.has(componentId: compId, entityId: identifier)
} }
/// Checks if this entity has any components. /// Checks if this entity has any components.
public var hasComponents: Bool { public var hasComponents: Bool {
return nexus.count(components: identifier) > 0 nexus.count(components: identifier) > 0
} }
/// Add one or more components to this entity. /// Add one or more components to this entity.
@ -74,14 +74,14 @@ public struct Entity {
/// - Parameter component: the component. /// - Parameter component: the component.
@discardableResult @discardableResult
public func remove<C>(_ component: C) -> Entity where C: Component { public func remove<C>(_ component: C) -> Entity where C: Component {
return remove(component.identifier) remove(component.identifier)
} }
/// Remove a component by type from this entity. /// Remove a component by type from this entity.
/// - Parameter compType: the component type. /// - Parameter compType: the component type.
@discardableResult @discardableResult
public func remove<C>(_ compType: C.Type) -> Entity where C: Component { public func remove<C>(_ compType: C.Type) -> Entity where C: Component {
return remove(compType.identifier) remove(compType.identifier)
} }
/// Remove a component by id from this entity. /// Remove a component by id from this entity.
@ -106,31 +106,41 @@ public struct Entity {
/// - Parameter entity: The child entity. /// - Parameter entity: The child entity.
@discardableResult @discardableResult
public func addChild(_ entity: Entity) -> Bool { public func addChild(_ entity: Entity) -> Bool {
return nexus.addChild(entity, to: self) nexus.addChild(entity, to: self)
} }
/// Remove entity as child. /// Remove entity as child.
/// - Parameter entity: The child entity. /// - Parameter entity: The child entity.
@discardableResult @discardableResult
public func removeChild(_ entity: Entity) -> Bool { public func removeChild(_ entity: Entity) -> Bool {
return nexus.removeChild(entity, from: self) nexus.removeChild(entity, from: self)
} }
/// Removes all children from this entity. /// Removes all children from this entity.
public func removeAllChildren() { public func removeAllChildren() {
return nexus.removeAllChildren(from: self) nexus.removeAllChildren(from: self)
} }
/// Returns the number of children for this entity. /// Returns the number of children for this entity.
public var numChildren: Int { public var numChildren: Int {
return nexus.numChildren(for: self) nexus.numChildren(for: self)
} }
} }
// MARK: - Equatable
extension Entity: Equatable { extension Entity: Equatable {
public static func == (lhs: Entity, rhs: Entity) -> Bool { public static func == (lhs: Entity, rhs: Entity) -> Bool {
return lhs.nexus == rhs.nexus && lhs.nexus === rhs.nexus && lhs.identifier == rhs.identifier
lhs.identifier == rhs.identifier }
}
extension Entity: CustomStringConvertible {
public var description: String {
"<Entity id:\(identifier.id)>"
}
}
extension Entity: CustomDebugStringConvertible {
public var debugDescription: String {
"<Entity id:\(identifier.id) numComponents:\(numComponents)>"
} }
} }

View File

@ -5,24 +5,18 @@
// Created by Christian Treffs on 08.10.17. // Created by Christian Treffs on 08.10.17.
// //
public struct EntityIdentifier: Identifiable { public struct EntityIdentifier {
/// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid. static let invalid = EntityIdentifier(.max)
public let id: Int
public init(_ uint32: UInt32) { /// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid.
@usableFromInline let id: Int
@usableFromInline
init(_ uint32: UInt32) {
self.id = Int(uint32) self.id = Int(uint32)
} }
} }
extension EntityIdentifier {
public static let invalid = EntityIdentifier(.max)
}
extension EntityIdentifier: Equatable { } extension EntityIdentifier: Equatable { }
extension EntityIdentifier: Hashable { } extension EntityIdentifier: Hashable { }
extension EntityIdentifier: Codable { } extension EntityIdentifier: Codable { }
extension EntityIdentifier: Comparable {
@inlinable
public static func < (lhs: EntityIdentifier, rhs: EntityIdentifier) -> Bool {
return lhs.id < rhs.id
}
}

View File

@ -18,39 +18,38 @@ public struct Family<R> where R: FamilyRequirementsManaging {
} }
@inlinable public var memberIds: UnorderedSparseSet<EntityIdentifier> { @inlinable public var memberIds: UnorderedSparseSet<EntityIdentifier> {
return nexus.members(withFamilyTraits: traits) nexus.members(withFamilyTraits: traits)
} }
@inlinable public var count: Int { @inlinable public var count: Int {
return memberIds.count memberIds.count
} }
@inlinable public var isEmpty: Bool { @inlinable public var isEmpty: Bool {
return memberIds.isEmpty memberIds.isEmpty
} }
@inlinable @inlinable
public func canBecomeMember(_ entity: Entity) -> Bool { public func canBecomeMember(_ entity: Entity) -> Bool {
return nexus.canBecomeMember(entity, in: traits) nexus.canBecomeMember(entity, in: traits)
} }
@inlinable @inlinable
public func isMember(_ entity: Entity) -> Bool { public func isMember(_ entity: Entity) -> Bool {
return nexus.isMember(entity, in: traits) nexus.isMember(entity, in: traits)
} }
} }
// MARK: - Equatable
extension Family: Equatable { extension Family: Equatable {
public static func == (lhs: Family<R>, rhs: Family<R>) -> Bool { public static func == (lhs: Family<R>, rhs: Family<R>) -> Bool {
return lhs.nexus == rhs.nexus && lhs.nexus === rhs.nexus &&
lhs.traits == rhs.traits lhs.traits == rhs.traits
} }
} }
extension Family: Sequence { extension Family: Sequence {
__consuming public func makeIterator() -> ComponentsIterator { __consuming public func makeIterator() -> ComponentsIterator {
return ComponentsIterator(family: self) ComponentsIterator(family: self)
} }
} }
@ -59,7 +58,7 @@ extension Family: LazySequenceProtocol { }
// MARK: - components iterator // MARK: - components iterator
extension Family { extension Family {
public struct ComponentsIterator: IteratorProtocol { public struct ComponentsIterator: IteratorProtocol {
@usableFromInline var memberIdsIterator: UnorderedSparseSetIterator<EntityIdentifier> @usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier>.ElementIterator
@usableFromInline unowned let nexus: Nexus @usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) { public init(family: Family<R>) {
@ -82,11 +81,11 @@ extension Family.ComponentsIterator: LazySequenceProtocol { }
// MARK: - entity iterator // MARK: - entity iterator
extension Family { extension Family {
@inlinable public var entities: EntityIterator { @inlinable public var entities: EntityIterator {
return EntityIterator(family: self) EntityIterator(family: self)
} }
public struct EntityIterator: IteratorProtocol { public struct EntityIterator: IteratorProtocol {
@usableFromInline var memberIdsIterator: UnorderedSparseSetIterator<EntityIdentifier> @usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier>.ElementIterator
@usableFromInline unowned let nexus: Nexus @usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) { public init(family: Family<R>) {
@ -108,11 +107,11 @@ extension Family.EntityIterator: LazySequenceProtocol { }
// MARK: - entity component iterator // MARK: - entity component iterator
extension Family { extension Family {
@inlinable public var entityAndComponents: EntityComponentIterator { @inlinable public var entityAndComponents: EntityComponentIterator {
return EntityComponentIterator(family: self) EntityComponentIterator(family: self)
} }
public struct EntityComponentIterator: IteratorProtocol { public struct EntityComponentIterator: IteratorProtocol {
@usableFromInline var memberIdsIterator: UnorderedSparseSetIterator<EntityIdentifier> @usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier>.ElementIterator
@usableFromInline unowned let nexus: Nexus @usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) { public init(family: Family<R>) {
@ -136,7 +135,7 @@ extension Family.EntityComponentIterator: LazySequenceProtocol { }
extension Family { extension Family {
@inlinable @inlinable
public func descendRelatives(from root: Entity) -> RelativesIterator { public func descendRelatives(from root: Entity) -> RelativesIterator {
return RelativesIterator(family: self, root: root) RelativesIterator(family: self, root: root)
} }
public struct RelativesIterator: IteratorProtocol { public struct RelativesIterator: IteratorProtocol {
@ -161,7 +160,7 @@ extension Family {
} }
mutating func aggregateRelativesBreathFirst(_ parent: EntityIdentifier) { mutating func aggregateRelativesBreathFirst(_ parent: EntityIdentifier) {
guard let children = nexus.parentChildrenMap[parent] else { guard let children = nexus.childrenByParentEntity[parent] else {
return return
} }
children children

View File

@ -37,8 +37,8 @@ extension Nexus {
requires componentA: A.Type, requires componentA: A.Type,
excludesAll excludedComponents: Component.Type... excludesAll excludedComponents: Component.Type...
) -> Family1<A> where A: Component { ) -> Family1<A> where A: Component {
return Family1<A>(nexus: self, Family1<A>(nexus: self,
requiresAll: componentA, requiresAll: componentA,
excludesAll: excludedComponents) excludesAll: excludedComponents)
} }
} }

View File

@ -43,7 +43,7 @@ extension Nexus {
_ componentB: B.Type, _ componentB: B.Type,
excludesAll excludedComponents: Component.Type... excludesAll excludedComponents: Component.Type...
) -> Family2<A, B> where A: Component, B: Component { ) -> Family2<A, B> where A: Component, B: Component {
return Family2<A, B>( Family2<A, B>(
nexus: self, nexus: self,
requiresAll: (componentA, componentB), requiresAll: (componentA, componentB),
excludesAll: excludedComponents excludesAll: excludedComponents

View File

@ -49,7 +49,7 @@ extension Nexus {
_ componentC: C.Type, _ componentC: C.Type,
excludesAll excludedComponents: Component.Type... excludesAll excludedComponents: Component.Type...
) -> Family3<A, B, C> where A: Component, B: Component, C: Component { ) -> Family3<A, B, C> where A: Component, B: Component, C: Component {
return Family3( Family3(
nexus: self, nexus: self,
requiresAll: (componentA, componentB, componentC), requiresAll: (componentA, componentB, componentC),
excludesAll: excludedComponents excludesAll: excludedComponents

View File

@ -55,7 +55,7 @@ extension Nexus {
_ componentD: D.Type, _ componentD: D.Type,
excludesAll excludedComponents: Component.Type... excludesAll excludedComponents: Component.Type...
) -> Family4<A, B, C, D> where A: Component, B: Component, C: Component, D: Component { ) -> Family4<A, B, C, D> where A: Component, B: Component, C: Component, D: Component {
return Family4( Family4(
nexus: self, nexus: self,
requiresAll: (componentA, componentB, componentC, componentD), requiresAll: (componentA, componentB, componentC, componentD),
excludesAll: excludedComponents excludesAll: excludedComponents

View File

@ -62,7 +62,7 @@ extension Nexus {
_ componentE: E.Type, _ componentE: E.Type,
excludesAll excludedComponents: Component.Type... excludesAll excludedComponents: Component.Type...
) -> Family5<A, B, C, D, E> where A: Component, B: Component, C: Component, D: Component, E: Component { ) -> Family5<A, B, C, D, E> where A: Component, B: Component, C: Component, D: Component, E: Component {
return Family5( Family5(
nexus: self, nexus: self,
requiresAll: (componentA, componentB, componentC, componentD, componentE), requiresAll: (componentA, componentB, componentC, componentD, componentE),
excludesAll: excludedComponents excludesAll: excludedComponents

View File

@ -23,50 +23,48 @@ public struct FamilyTraitSet {
self.setHash = FirebladeECS.hash(combine: [requiresAll, excludesAll]) self.setHash = FirebladeECS.hash(combine: [requiresAll, excludesAll])
} }
// MARK: - match
@inlinable @inlinable
public func isMatch(components: Set<ComponentIdentifier>) -> Bool { public func isMatch(components: Set<ComponentIdentifier>) -> Bool {
return hasAll(components) && hasNone(components) hasAll(components) && hasNone(components)
} }
@inlinable @inlinable
public func hasAll(_ components: Set<ComponentIdentifier>) -> Bool { public func hasAll(_ components: Set<ComponentIdentifier>) -> Bool {
return requiresAll.isSubset(of: components) requiresAll.isSubset(of: components)
} }
@inlinable @inlinable
public func hasNone(_ components: Set<ComponentIdentifier>) -> Bool { public func hasNone(_ components: Set<ComponentIdentifier>) -> Bool {
return excludesAll.isDisjoint(with: components) excludesAll.isDisjoint(with: components)
} }
// MARK: - valid
@inlinable @inlinable
public static func isValid(requiresAll: Set<ComponentIdentifier>, excludesAll: Set<ComponentIdentifier>) -> Bool { public static func isValid(requiresAll: Set<ComponentIdentifier>, excludesAll: Set<ComponentIdentifier>) -> Bool {
return !requiresAll.isEmpty && !requiresAll.isEmpty &&
requiresAll.isDisjoint(with: excludesAll) requiresAll.isDisjoint(with: excludesAll)
} }
} }
// MARK: - Equatable
extension FamilyTraitSet: Equatable { extension FamilyTraitSet: Equatable {
public static func == (lhs: FamilyTraitSet, rhs: FamilyTraitSet) -> Bool { public static func == (lhs: FamilyTraitSet, rhs: FamilyTraitSet) -> Bool {
return lhs.setHash == rhs.setHash lhs.setHash == rhs.setHash
} }
} }
// MARK: - Hashable
extension FamilyTraitSet: Hashable { extension FamilyTraitSet: Hashable {
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(setHash) hasher.combine(setHash)
} }
} }
extension FamilyTraitSet: CustomStringConvertible, CustomDebugStringConvertible { extension FamilyTraitSet: CustomStringConvertible {
@inlinable public var description: String { @inlinable public var description: String {
return "<FamilyTraitSet [requiresAll:\(requiresAll.description) excludesAll:\(excludesAll.description)]>" "<FamilyTraitSet [requiresAll:\(requiresAll.description) excludesAll:\(excludesAll.description)]>"
} }
}
@inlinable public var debugDescription: String {
return "<FamilyTraitSet [requiresAll:\(requiresAll.debugDescription) excludesAll: \(excludesAll.debugDescription)]>" extension FamilyTraitSet: CustomDebugStringConvertible {
@inlinable public var debugDescription: String {
"<FamilyTraitSet [requiresAll:\(requiresAll.debugDescription) excludesAll: \(excludesAll.debugDescription)]>"
} }
} }

View File

@ -83,3 +83,52 @@ extension EntityComponentHash {
return EntityIdentifier(UInt32(truncatingIfNeeded: entityId)) return EntityIdentifier(UInt32(truncatingIfNeeded: entityId))
} }
} }
// MARK: - string hashing
/// https://stackoverflow.com/a/52440609
public enum StringHashing {
/// *Waren Singer djb2*
///
/// <https://stackoverflow.com/a/43149500>
public static func singer_djb2(_ utf8String: String) -> UInt {
var hash = UInt(5381)
var iter = utf8String.unicodeScalars.makeIterator()
while let char = iter.next() {
hash = 127 * (hash & 0x00ffffffffffffff) + UInt(char.value)
}
return hash
}
/// *Dan Bernstein djb2*
///
/// This algorithm (k=33) was first reported by dan bernstein many years ago in comp.lang.c.
/// Another version of this algorithm (now favored by bernstein) uses xor: hash(i) = hash(i - 1) * 33 ^ str[i];
/// The magic of number 33 (why it works better than many other constants, prime or not) has never been adequately explained.
///
/// <http://www.cse.yorku.ca/~oz/hash.html>
public static func bernstein_djb2(_ string: String) -> UInt {
var hash: UInt = 5381
var iter = string.unicodeScalars.makeIterator()
while let char = iter.next() {
hash = (hash << 5) &+ hash &+ UInt(char.value)
//hash = ((hash << 5) + hash) + UInt(c.value)
}
return hash
}
/// *sdbm*
///
/// This algorithm was created for sdbm (a public-domain reimplementation of ndbm) database library.
/// It was found to do well in scrambling bits, causing better distribution of the keys and fewer splits.
/// It also happens to be a good general hashing function with good distribution.
///
/// <http://www.cse.yorku.ca/~oz/hash.html>
public static func sdbm(_ string: String) -> UInt {
var hash: UInt = 0
var iter = string.unicodeScalars.makeIterator()
while let char = iter.next() {
hash = (UInt(char.value) &+ (hash << 6) &+ (hash << 16))
}
return hash
}
}

View File

@ -1,34 +0,0 @@
//
// Identifiable.swift
//
//
// Created by Christian Treffs on 05.10.19.
//
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#if swift(<5.1)
/// A class of types whose instances hold the value of an entity with stable identity.
public protocol Identifiable {
/// A type representing the stable identity of the entity associated with `self`.
associatedtype ID: Hashable
/// The stable identity of the entity associated with `self`.
var id: ID { get }
}
extension Identifiable where Self: AnyObject {
public var id: ObjectIdentifier {
return ObjectIdentifier(self)
}
}
#endif

View File

@ -1,92 +0,0 @@
//
// ManagedContiguousArray.swift
// FirebladeECS
//
// Created by Christian Treffs on 28.10.17.
//
public class ManagedContiguousArray<Element> {
public typealias Index = Int
private let chunkSize: Int
private var size: Int = 0
private var store: ContiguousArray<Element?> = []
public init(minCount: Int = 4096) {
chunkSize = minCount
store = ContiguousArray<Element?>(repeating: nil, count: minCount)
}
deinit {
clear()
}
public var count: Int {
return size
}
@discardableResult
public func insert(_ element: Element, at index: Int) -> Bool {
if needsToGrow(index) {
grow(to: index)
}
if store[index] == nil {
size += 1
}
store[index] = element
return true
}
public func contains(_ index: Index) -> Bool {
if store.count <= index {
return false
}
return store[index] != nil
}
public func get(at index: Index) -> Element? {
return store[index]
}
public func get(unsafeAt index: Index) -> Element {
return store[index].unsafelyUnwrapped
}
@discardableResult
public func remove(at index: Index) -> Bool {
if store[index] != nil {
size -= 1
}
store[index] = nil
if size == 0 {
clear()
}
return true
}
public func clear(keepingCapacity: Bool = false) {
size = 0
store.removeAll(keepingCapacity: keepingCapacity)
}
private func needsToGrow(_ index: Index) -> Bool {
return index > store.count - 1
}
private func grow(to index: Index) {
let newCapacity: Int = calculateCapacity(to: index)
let newCount: Int = newCapacity - store.count
store += ContiguousArray<Element?>(repeating: nil, count: newCount)
}
private func calculateCapacity(to index: Index) -> Int {
let delta = Float(index) / Float(chunkSize)
let multiplier = Int(delta.rounded(.up)) + 1
return multiplier * chunkSize
}
}
// MARK: - Equatable
extension ManagedContiguousArray: Equatable where Element: Equatable {
public static func == (lhs: ManagedContiguousArray<Element>, rhs: ManagedContiguousArray<Element>) -> Bool {
return lhs.store == rhs.store
}
}

View File

@ -7,7 +7,7 @@
extension Nexus { extension Nexus {
public final var numComponents: Int { public final var numComponents: Int {
return componentsByType.reduce(0) { $0 + $1.value.count } componentsByType.reduce(0) { $0 + $1.value.count }
} }
public final func has(componentId: ComponentIdentifier, entityId: EntityIdentifier) -> Bool { public final func has(componentId: ComponentIdentifier, entityId: EntityIdentifier) -> Bool {
@ -18,14 +18,14 @@ extension Nexus {
} }
public final func count(components entityId: EntityIdentifier) -> Int { public final func count(components entityId: EntityIdentifier) -> Int {
return componentIdsByEntity[entityId]?.count ?? 0 componentIdsByEntity[entityId]?.count ?? 0
} }
public final func assign(component: Component, to entity: Entity) { public final func assign(component: Component, to entity: Entity) {
let componentId: ComponentIdentifier = component.identifier let componentId: ComponentIdentifier = component.identifier
let entityId: EntityIdentifier = entity.identifier let entityId: EntityIdentifier = entity.identifier
/// test if component is already assigned // test if component is already assigned
guard !has(componentId: componentId, entityId: entityId) else { guard !has(componentId: componentId, entityId: entityId) else {
delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)") delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)")
assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)") assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)")
@ -34,7 +34,7 @@ extension Nexus {
// add component instances to uniform component stores // add component instances to uniform component stores
if componentsByType[componentId] == nil { if componentsByType[componentId] == nil {
componentsByType[componentId] = ManagedContiguousArray<Component>() componentsByType[componentId] = UnorderedSparseSet<Component>()
} }
componentsByType[componentId]?.insert(component, at: entityId.id) componentsByType[componentId]?.insert(component, at: entityId.id)
@ -76,13 +76,13 @@ extension Nexus {
@inlinable @inlinable
public final func get<C>(unsafeComponentFor entityId: EntityIdentifier) -> C where C: Component { public final func get<C>(unsafeComponentFor entityId: EntityIdentifier) -> C where C: Component {
let component: Component = get(unsafeComponent: C.identifier, for: entityId) let component: Component = get(unsafeComponent: C.identifier, for: entityId)
/// components are guaranteed to be reference tyes so unsafeDowncast is applicable here // components are guaranteed to be reference tyes so unsafeDowncast is applicable here
return unsafeDowncast(component, to: C.self) return unsafeDowncast(component, to: C.self)
} }
@inlinable @inlinable
public final func get(components entityId: EntityIdentifier) -> Set<ComponentIdentifier>? { public final func get(components entityId: EntityIdentifier) -> Set<ComponentIdentifier>? {
return componentIdsByEntity[entityId] componentIdsByEntity[entityId]
} }
@discardableResult @discardableResult

View File

@ -17,10 +17,9 @@ extension Nexus {
@discardableResult @discardableResult
public func createEntity() -> Entity { public func createEntity() -> Entity {
let newEntityIdentifier: EntityIdentifier = nextEntityId() let newEntityIdentifier: EntityIdentifier = nextEntityId()
let newEntity = Entity(nexus: self, id: newEntityIdentifier) entityStorage.insert(newEntityIdentifier, at: newEntityIdentifier.id)
entityStorage.insert(newEntity, at: newEntityIdentifier.id)
delegate?.nexusEvent(EntityCreated(entityId: newEntityIdentifier)) delegate?.nexusEvent(EntityCreated(entityId: newEntityIdentifier))
return newEntity return Entity(nexus: self, id: newEntityIdentifier)
} }
@discardableResult @discardableResult
@ -32,31 +31,37 @@ extension Nexus {
/// Number of entities in nexus. /// Number of entities in nexus.
public var numEntities: Int { public var numEntities: Int {
return entityStorage.count entityStorage.count
} }
public func exists(entity entityId: EntityIdentifier) -> Bool { public func exists(entity entityId: EntityIdentifier) -> Bool {
return entityStorage.contains(entityId.id) entityStorage.contains(entityId.id)
} }
public func get(entity entityId: EntityIdentifier) -> Entity? { public func get(entity entityId: EntityIdentifier) -> Entity? {
return entityStorage.get(at: entityId.id) guard let id = entityStorage.get(at: entityId.id) else {
return nil
}
return Entity(nexus: self, id: id)
} }
public func get(unsafeEntity entityId: EntityIdentifier) -> Entity { public func get(unsafeEntity entityId: EntityIdentifier) -> Entity {
return entityStorage.get(unsafeAt: entityId.id) Entity(nexus: self, id: entityStorage.get(unsafeAt: entityId.id))
} }
@discardableResult @discardableResult
public func destroy(entity: Entity) -> Bool { public func destroy(entity: Entity) -> Bool {
let entityId: EntityIdentifier = entity.identifier self.destroy(entityId: entity.identifier)
}
@discardableResult
public func destroy(entityId: EntityIdentifier) -> Bool {
guard entityStorage.remove(at: entityId.id) != nil else { guard entityStorage.remove(at: entityId.id) != nil else {
delegate?.nexusNonFatalError("EntityRemove failure: no entity \(entityId) to remove") delegate?.nexusNonFatalError("EntityRemove failure: no entity \(entityId) to remove")
return false return false
} }
removeAllChildren(from: entity) removeAllChildren(from: entityId)
if removeAll(componentes: entityId) { if removeAll(componentes: entityId) {
update(familyMembership: entityId) update(familyMembership: entityId)

View File

@ -7,7 +7,7 @@
extension Nexus { extension Nexus {
public final var numFamilies: Int { public final var numFamilies: Int {
return familyMembersByTraits.keys.count familyMembersByTraits.keys.count
} }
public func canBecomeMember(_ entity: Entity, in traits: FamilyTraitSet) -> Bool { public func canBecomeMember(_ entity: Entity, in traits: FamilyTraitSet) -> Bool {
@ -19,18 +19,18 @@ extension Nexus {
} }
public func members(withFamilyTraits traits: FamilyTraitSet) -> UnorderedSparseSet<EntityIdentifier> { public func members(withFamilyTraits traits: FamilyTraitSet) -> UnorderedSparseSet<EntityIdentifier> {
return familyMembersByTraits[traits] ?? UnorderedSparseSet<EntityIdentifier>() familyMembersByTraits[traits] ?? UnorderedSparseSet<EntityIdentifier>()
} }
public func isMember(_ entity: Entity, in family: FamilyTraitSet) -> Bool { public func isMember(_ entity: Entity, in family: FamilyTraitSet) -> Bool {
return isMember(entity.identifier, in: family) isMember(entity.identifier, in: family)
} }
public func isMember(_ entityId: EntityIdentifier, in family: FamilyTraitSet) -> Bool { public func isMember(_ entityId: EntityIdentifier, in family: FamilyTraitSet) -> Bool {
return isMember(entity: entityId, inFamilyWithTraits: family) isMember(entity: entityId, inFamilyWithTraits: family)
} }
public func isMember(entity entityId: EntityIdentifier, inFamilyWithTraits traits: FamilyTraitSet) -> Bool { public func isMember(entity entityId: EntityIdentifier, inFamilyWithTraits traits: FamilyTraitSet) -> Bool {
return members(withFamilyTraits: traits).contains(entityId.id) members(withFamilyTraits: traits).contains(entityId.id)
} }
} }

View File

@ -13,14 +13,15 @@ extension Nexus {
} }
familyMembersByTraits[traits] = UnorderedSparseSet<EntityIdentifier>() familyMembersByTraits[traits] = UnorderedSparseSet<EntityIdentifier>()
defer { delegate?.nexusEvent(FamilyCreated(family: traits)) }
update(familyMembership: traits) update(familyMembership: traits)
} }
final func update(familyMembership traits: FamilyTraitSet) { final func update(familyMembership traits: FamilyTraitSet) {
// FIXME: iterating all entities is costly for many entities // FIXME: iterating all entities is costly for many entities
var iter = entityStorage.makeIterator() var iter = entityStorage.makeIterator()
while let entity = iter.next() { while let entityId = iter.next() {
update(membership: traits, for: entity.identifier) update(membership: traits, for: entityId)
} }
} }
@ -50,25 +51,26 @@ extension Nexus {
case (true, false): case (true, false):
add(entityWithId: entityId, toFamilyWithTraits: traits) add(entityWithId: entityId, toFamilyWithTraits: traits)
delegate?.nexusEvent(FamilyMemberAdded(member: entityId, toFamily: traits)) delegate?.nexusEvent(FamilyMemberAdded(member: entityId, toFamily: traits))
return
case (false, true): case (false, true):
remove(entityWithId: entityId, fromFamilyWithTraits: traits) remove(entityWithId: entityId, fromFamilyWithTraits: traits)
delegate?.nexusEvent(FamilyMemberRemoved(member: entityId, from: traits)) delegate?.nexusEvent(FamilyMemberRemoved(member: entityId, from: traits))
return
default: default:
return break
} }
} }
final func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) { final func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) {
precondition(familyMembersByTraits[traits] != nil) familyMembersByTraits[traits]!.insert(entityId, at: entityId.id)
familyMembersByTraits[traits].unsafelyUnwrapped.insert(entityId, at: entityId.id)
} }
final func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) { final func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) {
precondition(familyMembersByTraits[traits] != nil) familyMembersByTraits[traits]!.remove(at: entityId.id)
familyMembersByTraits[traits].unsafelyUnwrapped.remove(at: entityId.id) if familyMembersByTraits[traits]!.isEmpty {
// delete family if no more entities are present
familyMembersByTraits[traits] = nil
delegate?.nexusEvent(FamilyDestroyed(family: traits))
}
} }
} }

View File

@ -8,11 +8,11 @@
extension Nexus { extension Nexus {
public final func addChild(_ child: Entity, to parent: Entity) -> Bool { public final func addChild(_ child: Entity, to parent: Entity) -> Bool {
let inserted: Bool let inserted: Bool
if parentChildrenMap[parent.identifier] == nil { if childrenByParentEntity[parent.identifier] == nil {
parentChildrenMap[parent.identifier] = [child.identifier] childrenByParentEntity[parent.identifier] = [child.identifier]
inserted = true inserted = true
} else { } else {
let (isNewMember, _) = parentChildrenMap[parent.identifier]!.insert(child.identifier) let (isNewMember, _) = childrenByParentEntity[parent.identifier]!.insert(child.identifier)
inserted = isNewMember inserted = isNewMember
} }
if inserted { if inserted {
@ -22,12 +22,12 @@ extension Nexus {
} }
public final func removeChild(_ child: Entity, from parent: Entity) -> Bool { public final func removeChild(_ child: Entity, from parent: Entity) -> Bool {
return removeChild(child.identifier, from: parent.identifier) removeChild(child.identifier, from: parent.identifier)
} }
@discardableResult @discardableResult
public final func removeChild(_ child: EntityIdentifier, from parent: EntityIdentifier) -> Bool { public final func removeChild(_ child: EntityIdentifier, from parent: EntityIdentifier) -> Bool {
let removed: Bool = parentChildrenMap[parent]?.remove(child) != nil let removed: Bool = childrenByParentEntity[parent]?.remove(child) != nil
if removed { if removed {
delegate?.nexusEvent(ChildRemoved(parent: parent, child: child)) delegate?.nexusEvent(ChildRemoved(parent: parent, child: child))
} }
@ -35,11 +35,15 @@ extension Nexus {
} }
public final func removeAllChildren(from parent: Entity) { public final func removeAllChildren(from parent: Entity) {
parentChildrenMap[parent.identifier]?.forEach { removeChild($0, from: parent.identifier) } self.removeAllChildren(from: parent.identifier)
return parentChildrenMap[parent.identifier] = nil }
public final func removeAllChildren(from parentId: EntityIdentifier) {
childrenByParentEntity[parentId]?.forEach { removeChild($0, from: parentId) }
return childrenByParentEntity[parentId] = nil
} }
public final func numChildren(for entity: Entity) -> Int { public final func numChildren(for entity: Entity) -> Int {
return parentChildrenMap[entity.identifier]?.count ?? 0 childrenByParentEntity[entity.identifier]?.count ?? 0
} }
} }

View File

@ -6,85 +6,95 @@
// //
public final class Nexus { public final class Nexus {
public final weak var delegate: NexusEventDelegate?
/// Main entity storage. /// Main entity storage.
/// Entities are tightly packed by EntityIdentifier. /// Entities are tightly packed by EntityIdentifier.
@usableFromInline final var entityStorage: UnorderedSparseSet<Entity> @usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier>
/// Entity ids that are currently not used.
@usableFromInline final var freeEntities: [EntityIdentifier]
/// - Key: ComponentIdentifier aka component type. /// - Key: ComponentIdentifier aka component type.
/// - Value: Array of component instances of same type (uniform). /// - Value: Array of component instances of same type (uniform).
/// New component instances are appended. /// New component instances are appended.
@usableFromInline final var componentsByType: [ComponentIdentifier: ManagedContiguousArray<Component>] @usableFromInline final var componentsByType: [ComponentIdentifier: UnorderedSparseSet<Component>]
/// - Key: EntityIdentifier aka entity index /// - Key: EntityIdentifier aka entity index
/// - Value: Set of unique component types (ComponentIdentifier). /// - Value: Set of unique component types (ComponentIdentifier).
/// Each element is a component identifier associated with this entity. /// Each element is a component identifier associated with this entity.
@usableFromInline final var componentIdsByEntity: [EntityIdentifier: Set<ComponentIdentifier>] @usableFromInline final var componentIdsByEntity: [EntityIdentifier: Set<ComponentIdentifier>]
/// Entity ids that are currently not used. /// - Key: A parent entity id.
@usableFromInline final var freeEntities: ContiguousArray<EntityIdentifier> /// - Value: Adjacency Set of all associated children.
@usableFromInline final var childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>]
/// - Key: FamilyTraitSet aka component types that make up one distinct family. /// - Key: FamilyTraitSet aka component types that make up one distinct family.
/// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family. /// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family.
@usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier>] @usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier>]
/// - Key: A parent entity id. public final weak var delegate: NexusEventDelegate?
/// - Value: Adjacency Set of all associated children.
@usableFromInline final var parentChildrenMap: [EntityIdentifier: Set<EntityIdentifier>]
public init() { public convenience init() {
entityStorage = UnorderedSparseSet<Entity>() self.init(entityStorage: UnorderedSparseSet<EntityIdentifier>(),
componentsByType = [:] componentsByType: [:],
componentIdsByEntity = [:] componentsByEntity: [:],
freeEntities = ContiguousArray<EntityIdentifier>() freeEntities: [],
familyMembersByTraits = [:] familyMembersByTraits: [:],
parentChildrenMap = [:] childrenByParentEntity: [:])
} }
public final func clear() { internal init(entityStorage: UnorderedSparseSet<EntityIdentifier>,
var iter = entityStorage.makeIterator() componentsByType: [ComponentIdentifier: UnorderedSparseSet<Component>],
while let entity = iter.next() { componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
destroy(entity: entity) freeEntities: [EntityIdentifier],
} familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier>],
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>]) {
entityStorage.removeAll() self.entityStorage = entityStorage
freeEntities.removeAll() self.componentsByType = componentsByType
self.componentIdsByEntity = componentsByEntity
assert(entityStorage.isEmpty) self.freeEntities = freeEntities
assert(componentsByType.values.reduce(0) { $0 + $1.count } == 0) self.familyMembersByTraits = familyMembersByTraits
assert(componentIdsByEntity.values.reduce(0) { $0 + $1.count } == 0) self.childrenByParentEntity = childrenByParentEntity
assert(freeEntities.isEmpty)
assert(familyMembersByTraits.values.reduce(0) { $0 + $1.count } == 0)
componentsByType.removeAll()
componentIdsByEntity.removeAll()
familyMembersByTraits.removeAll()
parentChildrenMap.removeAll()
} }
deinit { deinit {
clear() clear()
} }
public final func clear() {
entityStorage.forEach { destroy(entityId: $0) }
entityStorage.removeAll()
freeEntities.removeAll()
componentsByType.removeAll()
componentIdsByEntity.removeAll()
familyMembersByTraits.removeAll()
childrenByParentEntity.removeAll()
}
public static var knownUniqueComponentTypes: Set<ComponentIdentifier> {
Set<ComponentIdentifier>(stableComponentIdentifierMap.keys.map { ComponentIdentifier(hash: $0) })
}
} }
// MARK: - Equatable // MARK: - centralized component identifier mapping
extension Nexus: Equatable { extension Nexus {
@inlinable internal static var stableComponentIdentifierMap: [ComponentIdentifier.Hash: ComponentIdentifier.StableId] = [:]
public static func == (lhs: Nexus, rhs: Nexus) -> Bool {
return lhs.entityStorage == rhs.entityStorage && internal static func makeOrGetComponentId<C>(_ componentType: C.Type) -> ComponentIdentifier.Hash where C: Component {
lhs.componentIdsByEntity == rhs.componentIdsByEntity && /// object identifier hash (only stable during runtime) - arbitrary hash is ok.
lhs.freeEntities == rhs.freeEntities && let objIdHash = ObjectIdentifier(componentType).hashValue
lhs.familyMembersByTraits == rhs.familyMembersByTraits && // if we do not know this component type yet - we register a stable identifier generator for it.
lhs.componentsByType.keys == rhs.componentsByType.keys && if stableComponentIdentifierMap[objIdHash] == nil {
lhs.parentChildrenMap == rhs.parentChildrenMap let string = String(describing: C.self)
// NOTE: components are not equatable (yet) let stableHash = StringHashing.singer_djb2(string)
stableComponentIdentifierMap[objIdHash] = stableHash
}
return objIdHash
} }
} }
// MARK: - CustomDebugStringConvertible // MARK: - CustomDebugStringConvertible
extension Nexus: CustomDebugStringConvertible { extension Nexus: CustomDebugStringConvertible {
public var debugDescription: String { public var debugDescription: String {
return "<Nexus entities:\(numEntities) components:\(numComponents) families:\(numFamilies)>" "<Nexus entities:\(numEntities) components:\(numComponents) families:\(numFamilies)>"
} }
} }

View File

@ -20,10 +20,6 @@ public struct ComponentAdded: NexusEvent {
public let toEntity: EntityIdentifier public let toEntity: EntityIdentifier
} }
public struct ComponentUpdated: NexusEvent {
public let atEnity: EntityIdentifier
}
public struct ComponentRemoved: NexusEvent { public struct ComponentRemoved: NexusEvent {
public let component: ComponentIdentifier public let component: ComponentIdentifier
public let from: EntityIdentifier public let from: EntityIdentifier

View File

@ -29,17 +29,23 @@ public struct Single<A> where A: SingleComponent {
public let entityId: EntityIdentifier public let entityId: EntityIdentifier
} }
extension Single: Equatable { } extension Single: Equatable {
public static func == (lhs: Single<A>, rhs: Single<A>) -> Bool {
lhs.traits == rhs.traits &&
lhs.entityId == rhs.entityId &&
lhs.nexus === rhs.nexus
}
}
extension Single where A: SingleComponent { extension Single where A: SingleComponent {
@inlinable public var component: A { @inlinable public var component: A {
/// Since we guarantee that the component will always be present by managing the complete lifecycle of the entity // Since we guarantee that the component will always be present by managing the complete lifecycle of the entity
/// and component assignment we may unsafelyUnwrap here. // and component assignment we may unsafelyUnwrap here.
/// Since components will allways be of reference type (class) we may use unsafeDowncast here for performance reasons. // Since components will allways be of reference type (class) we may use unsafeDowncast here for performance reasons.
return nexus.get(unsafeComponentFor: entityId) return nexus.get(unsafeComponentFor: entityId)
} }
public var entity: Entity { public var entity: Entity {
return nexus.get(entity: entityId).unsafelyUnwrapped nexus.get(entity: entityId).unsafelyUnwrapped
} }
} }

View File

@ -5,7 +5,7 @@
// Created by Christian Treffs on 30.10.17. // Created by Christian Treffs on 30.10.17.
// //
open class UnorderedSparseSet<Element> { public struct UnorderedSparseSet<Element> {
public typealias Index = Int public typealias Index = Int
public typealias Key = Int public typealias Key = Int
@ -18,20 +18,20 @@ open class UnorderedSparseSet<Element> {
@usableFromInline var sparse: [Index: Key] @usableFromInline var sparse: [Index: Key]
public init() { public init() {
sparse = [Index: Key]() self.init(sparse: [:], dense: [])
dense = ContiguousArray<Entry>()
} }
deinit { init(sparse: [Index: Key], dense: ContiguousArray<Entry>) {
removeAll() self.sparse = sparse
self.dense = dense
} }
public var count: Int { return dense.count } public var count: Int { dense.count }
public var isEmpty: Bool { return dense.isEmpty } public var isEmpty: Bool { dense.isEmpty }
@inlinable @inlinable
public func contains(_ key: Key) -> Bool { public func contains(_ key: Key) -> Bool {
return find(at: key) != nil find(at: key) != nil
} }
/// Inset an element for a given key into the set in O(1). /// Inset an element for a given key into the set in O(1).
@ -42,7 +42,7 @@ open class UnorderedSparseSet<Element> {
/// - key: the key /// - key: the key
/// - Returns: true if new, false if replaced. /// - Returns: true if new, false if replaced.
@discardableResult @discardableResult
public func insert(_ element: Element, at key: Key) -> Bool { public mutating func insert(_ element: Element, at key: Key) -> Bool {
if let (denseIndex, _) = find(at: key) { if let (denseIndex, _) = find(at: key) {
dense[denseIndex] = Entry(key: key, element: element) dense[denseIndex] = Entry(key: key, element: element)
return false return false
@ -69,7 +69,7 @@ open class UnorderedSparseSet<Element> {
@inlinable @inlinable
public func get(unsafeAt key: Key) -> Element { public func get(unsafeAt key: Key) -> Element {
return find(at: key).unsafelyUnwrapped.1 find(at: key).unsafelyUnwrapped.1
} }
/// Removes the element entry for given key in O(1). /// Removes the element entry for given key in O(1).
@ -77,7 +77,7 @@ open class UnorderedSparseSet<Element> {
/// - Parameter key: the key /// - Parameter key: the key
/// - Returns: removed value or nil if key not found. /// - Returns: removed value or nil if key not found.
@discardableResult @discardableResult
public func remove(at key: Key) -> Entry? { public mutating func remove(at key: Key) -> Entry? {
guard let (denseIndex, _) = find(at: key) else { guard let (denseIndex, _) = find(at: key) else {
return nil return nil
} }
@ -92,22 +92,17 @@ open class UnorderedSparseSet<Element> {
} }
@inlinable @inlinable
public func removeAll(keepingCapacity: Bool = false) { public mutating func removeAll(keepingCapacity: Bool = false) {
sparse.removeAll(keepingCapacity: keepingCapacity) sparse.removeAll(keepingCapacity: keepingCapacity)
dense.removeAll(keepingCapacity: keepingCapacity) dense.removeAll(keepingCapacity: keepingCapacity)
} }
@inlinable
public func makeIterator() -> UnorderedSparseSetIterator<Element> {
return UnorderedSparseSetIterator<Element>(self)
}
/// Removes an element from the set and retuns it in O(1). /// Removes an element from the set and retuns it in O(1).
/// The removed element is replaced with the last element of the set. /// The removed element is replaced with the last element of the set.
/// ///
/// - Parameter denseIndex: the dense index /// - Parameter denseIndex: the dense index
/// - Returns: the element entry /// - Returns: the element entry
private func swapRemove(at denseIndex: Int) -> Entry { private mutating func swapRemove(at denseIndex: Int) -> Entry {
dense.swapAt(denseIndex, dense.count - 1) dense.swapAt(denseIndex, dense.count - 1)
return dense.removeLast() return dense.removeLast()
} }
@ -128,7 +123,7 @@ open class UnorderedSparseSet<Element> {
@inlinable @inlinable
public subscript(position: Index) -> Element { public subscript(position: Index) -> Element {
get { get {
return get(unsafeAt: position) get(unsafeAt: position)
} }
set(newValue) { set(newValue) {
@ -137,30 +132,42 @@ open class UnorderedSparseSet<Element> {
} }
@inlinable public var first: Element? { @inlinable public var first: Element? {
return dense.first?.element dense.first?.element
} }
@inlinable public var last: Element? { @inlinable public var last: Element? {
return dense.last?.element dense.last?.element
} }
} }
// MARK: - Sequence
extension UnorderedSparseSet: Sequence {
public __consuming func makeIterator() -> ElementIterator {
ElementIterator(self)
}
// MARK: - UnorderedSparseSetIterator
public struct ElementIterator: IteratorProtocol {
public private(set) var iterator: IndexingIterator<ContiguousArray<UnorderedSparseSet<Element>.Entry>>
public init(_ sparseSet: UnorderedSparseSet<Element>) {
iterator = sparseSet.dense.makeIterator()
}
public mutating func next() -> Element? {
iterator.next()?.element
}
}
}
// MARK: - Equatable
extension UnorderedSparseSet.Entry: Equatable where Element: Equatable { } extension UnorderedSparseSet.Entry: Equatable where Element: Equatable { }
extension UnorderedSparseSet: Equatable where Element: Equatable { extension UnorderedSparseSet: Equatable where Element: Equatable {
public static func == (lhs: UnorderedSparseSet<Element>, rhs: UnorderedSparseSet<Element>) -> Bool { public static func == (lhs: UnorderedSparseSet<Element>, rhs: UnorderedSparseSet<Element>) -> Bool {
return lhs.dense == rhs.dense && lhs.sparse == rhs.sparse lhs.dense == rhs.dense && lhs.sparse == rhs.sparse
} }
} }
// MARK: - UnorderedSparseSetIterator // MARK: - Codable
public struct UnorderedSparseSetIterator<Element>: IteratorProtocol { extension UnorderedSparseSet.Entry: Codable where Element: Codable { }
public private(set) var iterator: IndexingIterator<ContiguousArray<UnorderedSparseSet<Element>.Entry>> extension UnorderedSparseSet: Codable where Element: Codable { }
public init(_ sparseSet: UnorderedSparseSet<Element>) {
iterator = sparseSet.dense.makeIterator()
}
public mutating func next() -> Element? {
return iterator.next()?.element
}
}

View File

@ -7,9 +7,12 @@
import FirebladeECS import FirebladeECS
class EmptyComponent: Component { } class EmptyComponent: Component {
}
class Name: Component { class Name: Component {
var name: String var name: String
init(name: String) { init(name: String) {
self.name = name self.name = name
@ -17,6 +20,7 @@ class Name: Component {
} }
class Position: Component { class Position: Component {
var x: Int var x: Int
var y: Int var y: Int
init(x: Int, y: Int) { init(x: Int, y: Int) {
@ -26,6 +30,7 @@ class Position: Component {
} }
class Velocity: Component { class Velocity: Component {
var a: Float var a: Float
init(a: Float) { init(a: Float) {
self.a = a self.a = a
@ -33,6 +38,7 @@ class Velocity: Component {
} }
class Party: Component { class Party: Component {
var partying: Bool var partying: Bool
init(partying: Bool) { init(partying: Bool) {
self.partying = partying self.partying = partying
@ -40,6 +46,7 @@ class Party: Component {
} }
class Color: Component { class Color: Component {
var r: UInt8 = 0 var r: UInt8 = 0
var g: UInt8 = 0 var g: UInt8 = 0
var b: UInt8 = 0 var b: UInt8 = 0

View File

@ -1,5 +1,5 @@
// //
// ComponentPerformanceTests.swift // ComponentIdentifierTests.swift
// FirebladeECSPerformanceTests // FirebladeECSPerformanceTests
// //
// Created by Christian Treffs on 14.02.19. // Created by Christian Treffs on 14.02.19.
@ -8,9 +8,11 @@
import FirebladeECS import FirebladeECS
import XCTest import XCTest
class ComponentTests: XCTestCase { class ComponentIdentifierTests: XCTestCase {
/// debug: 0.456 sec
func testMeasureStaticComponentIdentifier() { func testMeasureStaticComponentIdentifier() {
let number: Int = 10_000 let number: Int = 1_000_000
measure { measure {
for _ in 0..<number { for _ in 0..<number {
let id = Position.identifier let id = Position.identifier
@ -19,8 +21,9 @@ class ComponentTests: XCTestCase {
} }
} }
/// debug: 0.413 sec
func testMeasureComponentIdentifier() { func testMeasureComponentIdentifier() {
let number: Int = 10_000 let number: Int = 1_000_000
let pos = Position(x: 1, y: 2) let pos = Position(x: 1, y: 2)
measure { measure {
for _ in 0..<number { for _ in 0..<number {

View File

@ -8,7 +8,16 @@
import FirebladeECS import FirebladeECS
import XCTest import XCTest
#if DEBUG
let isDebug: Bool = true
#else
let isDebug: Bool = false
#endif
class HashingPerformanceTests: XCTestCase { class HashingPerformanceTests: XCTestCase {
/// release: 0.726 sec
/// debug: 3.179 sec
func testMeasureCombineHash() { func testMeasureCombineHash() {
let a: Set<Int> = Set<Int>([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812]) let a: Set<Int> = Set<Int>([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812])
let b: Set<Int> = Set<Int>([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234]) let b: Set<Int> = Set<Int>([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234])
@ -23,6 +32,8 @@ class HashingPerformanceTests: XCTestCase {
} }
} }
/// release: 0.494 sec
/// debug: 1.026 sec
func testMeasureSetOfSetHash() { func testMeasureSetOfSetHash() {
let a: Set<Int> = Set<Int>([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812]) let a: Set<Int> = Set<Int>([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812])
let b: Set<Int> = Set<Int>([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234]) let b: Set<Int> = Set<Int>([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234])
@ -36,4 +47,58 @@ class HashingPerformanceTests: XCTestCase {
} }
} }
} }
/// release: 0.098 sec
/// debug: 16.702 sec
func testMeasureBernsteinDjb2() throws {
try XCTSkipIf(isDebug)
let string = "The quick brown fox jumps over the lazy dog"
measure {
for _ in 0..<1_000_000 {
let hash = StringHashing.bernstein_djb2(string)
_ = hash
}
}
}
/// release: 0.087 sec
/// debug: 2.613 sec
func testMeasureSingerDjb2() throws {
let string = "The quick brown fox jumps over the lazy dog"
measure {
for _ in 0..<1_000_000 {
let hash = StringHashing.singer_djb2(string)
_ = hash
}
}
}
/// release: 0.088 sec
/// debug: 30.766 sec
func testMeasureSDBM() throws {
try XCTSkipIf(isDebug)
let string = "The quick brown fox jumps over the lazy dog"
measure {
for _ in 0..<1_000_000 {
let hash = StringHashing.sdbm(string)
_ = hash
}
}
}
/// release: 0.036 sec
/// debug: 0.546 sec
func testMeasureSwiftHasher() throws {
try XCTSkipIf(isDebug)
let string = "The quick brown fox jumps over the lazy dog"
measure {
for _ in 0..<1_000_000 {
var hasher = Hasher()
hasher.combine(string)
let hash = hasher.finalize()
_ = hash
}
}
}
} }

View File

@ -0,0 +1,94 @@
//
// TypeIdentifierPerformanceTests.swift
//
//
// Created by Christian Treffs on 05.10.19.
//
import FirebladeECS
import XCTest
final class TypeIdentifierPerformanceTests: XCTestCase {
let maxIterations: Int = 100_000
// release: 0.000 sec
// debug: 0.051 sec
func testPerformanceObjectIdentifier() {
measure {
for _ in 0..<maxIterations {
_ = ObjectIdentifier(Color.self)
_ = ObjectIdentifier(EmptyComponent.self)
_ = ObjectIdentifier(Name.self)
_ = ObjectIdentifier(Party.self)
_ = ObjectIdentifier(Position.self)
_ = ObjectIdentifier(SingleGameState.self)
_ = ObjectIdentifier(Velocity.self)
}
}
}
/// release: 1.034 sec
/// debug:
func testPerformanceHash() {
measure {
for _ in 0..<maxIterations {
_ = StringHashing.singer_djb2(String(describing: Color.self))
_ = StringHashing.singer_djb2(String(describing: EmptyComponent.self))
_ = StringHashing.singer_djb2(String(describing: Name.self))
_ = StringHashing.singer_djb2(String(describing: Party.self))
_ = StringHashing.singer_djb2(String(describing: Position.self))
_ = StringHashing.singer_djb2(String(describing: SingleGameState.self))
_ = StringHashing.singer_djb2(String(describing: Velocity.self))
}
}
}
/// release: 1.034 sec
/// debug: 1.287 sec
func testPerformanceStringDescribing() {
measure {
for _ in 0..<maxIterations {
_ = String(describing: Color.self)
_ = String(describing: EmptyComponent.self)
_ = String(describing: Name.self)
_ = String(describing: Party.self)
_ = String(describing: Position.self)
_ = String(describing: SingleGameState.self)
_ = String(describing: Velocity.self)
}
}
}
/// release: 1.187 sec
/// debug: 1.498 sec
func testPerformanceStringReflecting() {
measure {
for _ in 0..<maxIterations {
_ = String(reflecting: Color.self)
_ = String(reflecting: EmptyComponent.self)
_ = String(reflecting: Name.self)
_ = String(reflecting: Party.self)
_ = String(reflecting: Position.self)
_ = String(reflecting: SingleGameState.self)
_ = String(reflecting: Velocity.self)
}
}
}
/// release: 2.102 sec
/// debug: 2.647 sec
func testPerformanceMirrorReflectingDescription() {
measure {
for _ in 0..<maxIterations {
_ = Mirror(reflecting: Color.self).description
_ = Mirror(reflecting: EmptyComponent.self).description
_ = Mirror(reflecting: Name.self).description
_ = Mirror(reflecting: Party.self).description
_ = Mirror(reflecting: Position.self).description
_ = Mirror(reflecting: SingleGameState.self).description
_ = Mirror(reflecting: Velocity.self).description
}
}
}
}

View File

@ -30,6 +30,8 @@ class TypedFamilyPerformanceTests: XCTestCase {
super.tearDown() super.tearDown()
} }
/// release: 0.011 sec
/// debug: 0.017 sec
func testMeasureTraitMatching() { func testMeasureTraitMatching() {
let a = nexus.createEntity() let a = nexus.createEntity()
a.assign(Position(x: 1, y: 2)) a.assign(Position(x: 1, y: 2))
@ -48,6 +50,8 @@ class TypedFamilyPerformanceTests: XCTestCase {
} }
} }
/// release: 0.001 sec
/// debug: 0.008 sec
func testPerformanceTypedFamilyEntities() { func testPerformanceTypedFamilyEntities() {
let family = nexus.family(requires: Position.self, excludesAll: Party.self) let family = nexus.family(requires: Position.self, excludesAll: Party.self)
@ -71,6 +75,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// debug: 0.004 sec
func testPerformanceArray() { func testPerformanceArray() {
let positions = [Position](repeating: Position(x: Int.random(in: 0...10), y: Int.random(in: 0...10)), count: numEntities) let positions = [Position](repeating: Position(x: Int.random(in: 0...10), y: Int.random(in: 0...10)), count: numEntities)
@ -87,6 +92,8 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, numEntities * 10) XCTAssertEqual(loopCount, numEntities * 10)
} }
/// release: 0.003 sec
/// debug: 0.010 sec
func testPerformanceTypedFamilyOneComponent() { func testPerformanceTypedFamilyOneComponent() {
let family = nexus.family(requires: Position.self, excludesAll: Party.self) let family = nexus.family(requires: Position.self, excludesAll: Party.self)
@ -109,6 +116,8 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.004 sec
/// debug: 0.016 sec
func testPerformanceTypedFamilyEntityOneComponent() { func testPerformanceTypedFamilyEntityOneComponent() {
let family = nexus.family(requires: Position.self, excludesAll: Party.self) let family = nexus.family(requires: Position.self, excludesAll: Party.self)
@ -133,6 +142,8 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.005 sec
/// debug: 0.016 sec
func testPerformanceTypedFamilyTwoComponents() { func testPerformanceTypedFamilyTwoComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self) let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self)
@ -156,6 +167,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.006 sec
func testPerformanceTypedFamilyEntityTwoComponents() { func testPerformanceTypedFamilyEntityTwoComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self) let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self)
@ -181,6 +193,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.007 sec
func testPerformanceTypedFamilyThreeComponents() { func testPerformanceTypedFamilyThreeComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self) let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self)
@ -205,6 +218,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.008 sec
func testPerformanceTypedFamilyEntityThreeComponents() { func testPerformanceTypedFamilyEntityThreeComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self) let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self)
@ -231,6 +245,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.009 sec
func testPerformanceTypedFamilyFourComponents() { func testPerformanceTypedFamilyFourComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self) let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self)
@ -256,6 +271,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.010 sec
func testPerformanceTypedFamilyEntityFourComponents() { func testPerformanceTypedFamilyEntityFourComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self) let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self)
@ -283,6 +299,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.012 sec
func testPerformanceTypedFamilyFiveComponents() { func testPerformanceTypedFamilyFiveComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self) let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self)
@ -308,6 +325,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10) XCTAssertEqual(loopCount, family.count * 10)
} }
/// release: 0.012 sec
func testPerformanceTypedFamilyEntityFiveComponents() { func testPerformanceTypedFamilyEntityFiveComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self) let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self)

View File

@ -1,11 +1,11 @@
#if !canImport(ObjectiveC) #if !canImport(ObjectiveC)
import XCTest import XCTest
extension ComponentTests { extension ComponentIdentifierTests {
// DO NOT MODIFY: This is autogenerated, use: // DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain` // `swift test --generate-linuxmain`
// to regenerate. // to regenerate.
static let __allTests__ComponentTests = [ static let __allTests__ComponentIdentifierTests = [
("testMeasureComponentIdentifier", testMeasureComponentIdentifier), ("testMeasureComponentIdentifier", testMeasureComponentIdentifier),
("testMeasureStaticComponentIdentifier", testMeasureStaticComponentIdentifier) ("testMeasureStaticComponentIdentifier", testMeasureStaticComponentIdentifier)
] ]
@ -16,8 +16,25 @@ extension HashingPerformanceTests {
// `swift test --generate-linuxmain` // `swift test --generate-linuxmain`
// to regenerate. // to regenerate.
static let __allTests__HashingPerformanceTests = [ static let __allTests__HashingPerformanceTests = [
("testMeasureBernsteinDjb2", testMeasureBernsteinDjb2),
("testMeasureCombineHash", testMeasureCombineHash), ("testMeasureCombineHash", testMeasureCombineHash),
("testMeasureSetOfSetHash", testMeasureSetOfSetHash) ("testMeasureSDBM", testMeasureSDBM),
("testMeasureSetOfSetHash", testMeasureSetOfSetHash),
("testMeasureSingerDjb2", testMeasureSingerDjb2),
("testMeasureSwiftHasher", testMeasureSwiftHasher)
]
}
extension TypeIdentifierPerformanceTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__TypeIdentifierPerformanceTests = [
("testPerformanceHash", testPerformanceHash),
("testPerformanceMirrorReflectingDescription", testPerformanceMirrorReflectingDescription),
("testPerformanceObjectIdentifier", testPerformanceObjectIdentifier),
("testPerformanceStringDescribing", testPerformanceStringDescribing),
("testPerformanceStringReflecting", testPerformanceStringReflecting)
] ]
} }
@ -44,8 +61,9 @@ extension TypedFamilyPerformanceTests {
public func __allTests() -> [XCTestCaseEntry] { public func __allTests() -> [XCTestCaseEntry] {
return [ return [
testCase(ComponentTests.__allTests__ComponentTests), testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
testCase(HashingPerformanceTests.__allTests__HashingPerformanceTests), testCase(HashingPerformanceTests.__allTests__HashingPerformanceTests),
testCase(TypeIdentifierPerformanceTests.__allTests__TypeIdentifierPerformanceTests),
testCase(TypedFamilyPerformanceTests.__allTests__TypedFamilyPerformanceTests) testCase(TypedFamilyPerformanceTests.__allTests__TypedFamilyPerformanceTests)
] ]
} }

View File

@ -1,41 +0,0 @@
//
// AccessTests.swift
//
//
// Created by Christian Treffs on 25.06.19.
//
import FirebladeECS
import XCTest
#if swift(>=5.1)
class AccessTests: XCTestCase {
func testReadOnly() {
let pos = Position(x: 1, y: 2)
let readable = ReadableOnly<Position>(pos)
XCTAssertEqual(readable.x, 1)
XCTAssertEqual(readable.y, 2)
// readable.x = 3 // does not work and that's correct!
}
func testWrite() {
let pos = Position(x: 1, y: 2)
let writable = Writable<Position>(pos)
XCTAssertEqual(writable.x, 1)
XCTAssertEqual(writable.y, 2)
writable.x = 3
XCTAssertEqual(writable.x, 3)
XCTAssertEqual(pos.x, 3)
XCTAssertEqual(writable.y, 2)
XCTAssertEqual(pos.y, 2)
}
}
#endif

View File

@ -7,7 +7,9 @@
import FirebladeECS import FirebladeECS
class EmptyComponent: Component { } class EmptyComponent: Component {
}
class Name: Component { class Name: Component {
var name: String var name: String

View File

@ -0,0 +1,22 @@
//
// ComponentIdentifierTests.swift
//
//
// Created by Christian Treffs on 05.10.19.
//
import XCTest
final class ComponentIdentifierTests: XCTestCase {
func testMirrorAsStableIdentifier() {
let m = String(reflecting: Position.self)
let identifier: String = m
XCTAssertEqual(identifier, "FirebladeECSTests.Position")
}
func testStringDescribingAsStableIdentifier() {
let s = String(describing: Position.self)
let identifier: String = s
XCTAssertEqual(identifier, "Position")
}
}

View File

@ -5,7 +5,8 @@
// Created by Christian Treffs on 22.10.17. // Created by Christian Treffs on 22.10.17.
// //
import FirebladeECS #if DEBUG
@testable import FirebladeECS
import XCTest import XCTest
class EntityTests: XCTestCase { class EntityTests: XCTestCase {
@ -21,9 +22,5 @@ class EntityTests: XCTestCase {
XCTAssertEqual(max, EntityIdentifier.invalid) XCTAssertEqual(max, EntityIdentifier.invalid)
XCTAssertEqual(max.id, Int(UInt32.max)) XCTAssertEqual(max.id, Int(UInt32.max))
} }
func testEntityIdentifierComparison() {
XCTAssertTrue(EntityIdentifier(1) < EntityIdentifier(2))
XCTAssertTrue(EntityIdentifier(23) > EntityIdentifier(4))
}
} }
#endif

View File

@ -5,6 +5,7 @@
// Created by Christian Treffs on 09.10.17. // Created by Christian Treffs on 09.10.17.
// //
#if DEBUG
@testable import FirebladeECS @testable import FirebladeECS
import XCTest import XCTest
@ -31,7 +32,6 @@ class FamilyTests: XCTestCase {
let family = nexus.family(requires: Position.self, let family = nexus.family(requires: Position.self,
excludesAll: Name.self) excludesAll: Name.self)
XCTAssertEqual(family.nexus, self.nexus)
XCTAssertTrue(family.nexus === self.nexus) XCTAssertTrue(family.nexus === self.nexus)
XCTAssertEqual(nexus.numFamilies, 1) XCTAssertEqual(nexus.numFamilies, 1)
XCTAssertEqual(nexus.numComponents, 0) XCTAssertEqual(nexus.numComponents, 0)
@ -73,11 +73,11 @@ class FamilyTests: XCTestCase {
XCTAssertEqual(nexus.numComponents, 1) XCTAssertEqual(nexus.numComponents, 1)
XCTAssertEqual(nexus.numEntities, 1) XCTAssertEqual(nexus.numEntities, 1)
entity.remove(Position.self) entity.remove(Position.self)
XCTAssertEqual(nexus.numFamilies, 1) XCTAssertEqual(nexus.numFamilies, 0)
XCTAssertEqual(nexus.numComponents, 0) XCTAssertEqual(nexus.numComponents, 0)
XCTAssertEqual(nexus.numEntities, 1) XCTAssertEqual(nexus.numEntities, 1)
nexus.destroy(entity: entity) nexus.destroy(entity: entity)
XCTAssertEqual(nexus.numFamilies, 1) XCTAssertEqual(nexus.numFamilies, 0)
XCTAssertEqual(nexus.numComponents, 0) XCTAssertEqual(nexus.numComponents, 0)
XCTAssertEqual(nexus.numEntities, 0) XCTAssertEqual(nexus.numEntities, 0)
} }
@ -132,7 +132,7 @@ class FamilyTests: XCTestCase {
entity.remove(velocity) entity.remove(velocity)
} }
XCTAssertEqual(familyA.count, 10) XCTAssertEqual(familyA.count, 0)
XCTAssertEqual(familyB.count, 0) XCTAssertEqual(familyB.count, 0)
} }
@ -186,3 +186,4 @@ class FamilyTests: XCTestCase {
XCTAssertEqual(family.memberIds.count, count + (count / 2)) XCTAssertEqual(family.memberIds.count, count + (count / 2))
} }
} }
#endif

View File

@ -5,6 +5,7 @@
// Created by Christian Treffs on 16.10.17. // Created by Christian Treffs on 16.10.17.
// //
#if DEBUG
@testable import FirebladeECS @testable import FirebladeECS
import XCTest import XCTest
@ -50,4 +51,18 @@ class HashingTests: XCTestCase {
XCTAssert(EntityComponentHash.decompose(h, with: entityId) == cH) XCTAssert(EntityComponentHash.decompose(h, with: entityId) == cH)
} }
} }
func testStringHashes() throws {
let string = "EiMersaufEn1"
XCTAssertEqual(StringHashing.bernstein_djb2(string), 13447802024599246090)
XCTAssertEqual(StringHashing.singer_djb2(string), 5428736256651916664)
XCTAssertEqual(StringHashing.sdbm(string), 15559770072020577201)
XCTAssertEqual(StringHashing.bernstein_djb2("gamedev"), 229466792000542)
XCTAssertEqual(StringHashing.singer_djb2("gamedev"), 2867840411746895486)
XCTAssertEqual(StringHashing.sdbm("gamedev"), 2761443862055442870)
}
} }
#endif

View File

@ -5,7 +5,8 @@
// Created by Christian Treffs on 09.10.17. // Created by Christian Treffs on 09.10.17.
// //
import FirebladeECS #if DEBUG
@testable import FirebladeECS
import XCTest import XCTest
class NexusTests: XCTestCase { class NexusTests: XCTestCase {
@ -33,9 +34,6 @@ class NexusTests: XCTestCase {
XCTAssert(e1.identifier.id == 1) XCTAssert(e1.identifier.id == 1)
XCTAssert(nexus.numEntities == 2) XCTAssert(nexus.numEntities == 2)
//FIXME: XCTAssertNil(e0.name)
//FIXME: XCTAssertEqual(e1.name, "Entity 1")
} }
func testEntityDestroy() { func testEntityDestroy() {
@ -158,3 +156,4 @@ class NexusTests: XCTestCase {
XCTAssert(pB.y != pA.y) XCTAssert(pB.y != pA.y)
} }
} }
#endif

View File

@ -5,6 +5,7 @@
// Created by Christian Treffs on 13.02.19. // Created by Christian Treffs on 13.02.19.
// //
#if DEBUG
@testable import FirebladeECS @testable import FirebladeECS
import XCTest import XCTest
@ -23,7 +24,6 @@ class SingleTests: XCTestCase {
func testSingleCreation() { func testSingleCreation() {
let single = nexus.single(SingleGameState.self) let single = nexus.single(SingleGameState.self)
XCTAssertEqual(single.nexus, self.nexus)
XCTAssertTrue(single.nexus === self.nexus) XCTAssertTrue(single.nexus === self.nexus)
XCTAssertEqual(single.traits.requiresAll.count, 1) XCTAssertEqual(single.traits.requiresAll.count, 1)
XCTAssertEqual(single.traits.excludesAll.count, 0) XCTAssertEqual(single.traits.excludesAll.count, 0)
@ -63,3 +63,4 @@ class SingleTests: XCTestCase {
XCTAssertTrue(singleGame === single.component) XCTAssertTrue(singleGame === single.component)
} }
} }
#endif

View File

@ -5,6 +5,7 @@
// Created by Christian Treffs on 31.10.17. // Created by Christian Treffs on 31.10.17.
// //
#if DEBUG
@testable import FirebladeECS @testable import FirebladeECS
import XCTest import XCTest
@ -387,7 +388,7 @@ class SparseSetTests: XCTestCase {
func testSparseSetDoubleRemove() { func testSparseSetDoubleRemove() {
class AClass { } class AClass { }
let set = UnorderedSparseSet<AClass>() var set = UnorderedSparseSet<AClass>()
let a = AClass() let a = AClass()
let b = AClass() let b = AClass()
set.insert(a, at: 0) set.insert(a, at: 0)
@ -471,7 +472,7 @@ class SparseSetTests: XCTestCase {
} }
func testSparseSetReduce() { func testSparseSetReduce() {
let characters = UnorderedSparseSet<Character>() var characters = UnorderedSparseSet<Character>()
characters.insert("H", at: 4) characters.insert("H", at: 4)
characters.insert("e", at: 13) characters.insert("e", at: 13)
@ -497,7 +498,7 @@ class SparseSetTests: XCTestCase {
} }
func testSubscript() { func testSubscript() {
let characters = UnorderedSparseSet<Character>() var characters = UnorderedSparseSet<Character>()
characters[4] = "H" characters[4] = "H"
characters[13] = "e" characters[13] = "e"
@ -528,7 +529,7 @@ class SparseSetTests: XCTestCase {
} }
func testStartEndIndex() { func testStartEndIndex() {
let set = UnorderedSparseSet<Character>() var set = UnorderedSparseSet<Character>()
set.insert("C", at: 33) set.insert("C", at: 33)
set.insert("A", at: 11) set.insert("A", at: 11)
@ -539,3 +540,4 @@ class SparseSetTests: XCTestCase {
XCTAssertEqual(mapped, ["C", "A", "B"]) XCTAssertEqual(mapped, ["C", "A", "B"])
} }
} }
#endif

View File

@ -5,6 +5,7 @@
// Created by Christian Treffs on 10.05.18. // Created by Christian Treffs on 10.05.18.
// //
#if DEBUG
@testable import FirebladeECS @testable import FirebladeECS
import XCTest import XCTest
@ -124,3 +125,4 @@ class SystemsTests: XCTestCase {
} }
} }
} }
#endif

View File

@ -1,6 +1,16 @@
#if !canImport(ObjectiveC) #if !canImport(ObjectiveC)
import XCTest import XCTest
extension ComponentIdentifierTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentIdentifierTests = [
("testMirrorAsStableIdentifier", testMirrorAsStableIdentifier),
("testStringDescribingAsStableIdentifier", testStringDescribingAsStableIdentifier)
]
}
extension ComponentTests { extension ComponentTests {
// DO NOT MODIFY: This is autogenerated, use: // DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain` // `swift test --generate-linuxmain`
@ -15,8 +25,7 @@ extension EntityTests {
// `swift test --generate-linuxmain` // `swift test --generate-linuxmain`
// to regenerate. // to regenerate.
static let __allTests__EntityTests = [ static let __allTests__EntityTests = [
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex), ("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex)
("testEntityIdentifierComparison", testEntityIdentifierComparison)
] ]
} }
@ -50,7 +59,8 @@ extension HashingTests {
// `swift test --generate-linuxmain` // `swift test --generate-linuxmain`
// to regenerate. // to regenerate.
static let __allTests__HashingTests = [ static let __allTests__HashingTests = [
("testCollisionsInCritialRange", testCollisionsInCritialRange) ("testCollisionsInCritialRange", testCollisionsInCritialRange),
("testStringHashes", testStringHashes)
] ]
} }
@ -124,6 +134,7 @@ extension SystemsTests {
public func __allTests() -> [XCTestCaseEntry] { public func __allTests() -> [XCTestCaseEntry] {
return [ return [
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
testCase(ComponentTests.__allTests__ComponentTests), testCase(ComponentTests.__allTests__ComponentTests),
testCase(EntityTests.__allTests__EntityTests), testCase(EntityTests.__allTests__EntityTests),
testCase(FamilyTests.__allTests__FamilyTests), testCase(FamilyTests.__allTests__FamilyTests),