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*
Icon
Network Trash Folder
Package.resolved
Packages
playground.xcworkspace
Temporary Items

View File

@ -1,4 +1,4 @@
image: swift:5.0
image: swift:5.2.2
#before_script:
#- 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_function_chains
- multiline_parameters
- multiline_parameters_brackets
#- multiline_parameters_brackets
- nimble_operator
- no_extension_access_modifier
- number_separator

View File

@ -11,14 +11,4 @@ script:
- swift package reset
- swift build
- 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:
swiftlint autocorrect --format
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:
swift test --generate-linuxmain
swiftlint autocorrect --format --path Tests/
@ -9,6 +25,21 @@ genLinuxTests:
test: genLinuxTests
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:
swift package reset
rm -rdf .swiftpm/xcode
@ -19,19 +50,7 @@ clean:
cleanArtifacts:
swift package clean
genXcode:
swift package generate-xcodeproj --enable-code-coverage --skip-extra-files
latest:
swift package update
resolve:
swift package resolve
genXcodeOpen: genXcode
open *.xcodeproj
precommit: lint genLinuxTests
# Test links in README
# requires <https://github.com/tcort/markdown-link-check>
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)
[![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)
[![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-linux-brightgreen.svg)](#)
@ -21,7 +21,7 @@ These instructions will get you a copy of the project up and running on your loc
### 💻 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.

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

View File

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

View File

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

View File

@ -24,24 +24,24 @@ public struct Entity {
/// Returns the number of components for this entity.
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.
/// - Parameter type: the component type.
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.
/// - Parameter compId: the component identifier.
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.
public var hasComponents: Bool {
return nexus.count(components: identifier) > 0
nexus.count(components: identifier) > 0
}
/// Add one or more components to this entity.
@ -74,14 +74,14 @@ public struct Entity {
/// - Parameter component: the component.
@discardableResult
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.
/// - Parameter compType: the component type.
@discardableResult
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.
@ -106,31 +106,41 @@ public struct Entity {
/// - Parameter entity: The child entity.
@discardableResult
public func addChild(_ entity: Entity) -> Bool {
return nexus.addChild(entity, to: self)
nexus.addChild(entity, to: self)
}
/// Remove entity as child.
/// - Parameter entity: The child entity.
@discardableResult
public func removeChild(_ entity: Entity) -> Bool {
return nexus.removeChild(entity, from: self)
nexus.removeChild(entity, from: self)
}
/// Removes all children from this entity.
public func removeAllChildren() {
return nexus.removeAllChildren(from: self)
nexus.removeAllChildren(from: self)
}
/// Returns the number of children for this entity.
public var numChildren: Int {
return nexus.numChildren(for: self)
nexus.numChildren(for: self)
}
}
// MARK: - Equatable
extension Entity: Equatable {
public static func == (lhs: Entity, rhs: Entity) -> Bool {
return lhs.nexus == rhs.nexus &&
lhs.identifier == rhs.identifier
lhs.nexus === rhs.nexus && 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.
//
public struct EntityIdentifier: Identifiable {
/// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid.
public let id: Int
public struct EntityIdentifier {
static let invalid = EntityIdentifier(.max)
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)
}
}
extension EntityIdentifier {
public static let invalid = EntityIdentifier(.max)
}
extension EntityIdentifier: Equatable { }
extension EntityIdentifier: Hashable { }
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> {
return nexus.members(withFamilyTraits: traits)
nexus.members(withFamilyTraits: traits)
}
@inlinable public var count: Int {
return memberIds.count
memberIds.count
}
@inlinable public var isEmpty: Bool {
return memberIds.isEmpty
memberIds.isEmpty
}
@inlinable
public func canBecomeMember(_ entity: Entity) -> Bool {
return nexus.canBecomeMember(entity, in: traits)
nexus.canBecomeMember(entity, in: traits)
}
@inlinable
public func isMember(_ entity: Entity) -> Bool {
return nexus.isMember(entity, in: traits)
nexus.isMember(entity, in: traits)
}
}
// MARK: - Equatable
extension Family: Equatable {
public static func == (lhs: Family<R>, rhs: Family<R>) -> Bool {
return lhs.nexus == rhs.nexus &&
lhs.nexus === rhs.nexus &&
lhs.traits == rhs.traits
}
}
extension Family: Sequence {
__consuming public func makeIterator() -> ComponentsIterator {
return ComponentsIterator(family: self)
ComponentsIterator(family: self)
}
}
@ -59,7 +58,7 @@ extension Family: LazySequenceProtocol { }
// MARK: - components iterator
extension Family {
public struct ComponentsIterator: IteratorProtocol {
@usableFromInline var memberIdsIterator: UnorderedSparseSetIterator<EntityIdentifier>
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier>.ElementIterator
@usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) {
@ -82,11 +81,11 @@ extension Family.ComponentsIterator: LazySequenceProtocol { }
// MARK: - entity iterator
extension Family {
@inlinable public var entities: EntityIterator {
return EntityIterator(family: self)
EntityIterator(family: self)
}
public struct EntityIterator: IteratorProtocol {
@usableFromInline var memberIdsIterator: UnorderedSparseSetIterator<EntityIdentifier>
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier>.ElementIterator
@usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) {
@ -108,11 +107,11 @@ extension Family.EntityIterator: LazySequenceProtocol { }
// MARK: - entity component iterator
extension Family {
@inlinable public var entityAndComponents: EntityComponentIterator {
return EntityComponentIterator(family: self)
EntityComponentIterator(family: self)
}
public struct EntityComponentIterator: IteratorProtocol {
@usableFromInline var memberIdsIterator: UnorderedSparseSetIterator<EntityIdentifier>
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier>.ElementIterator
@usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) {
@ -136,7 +135,7 @@ extension Family.EntityComponentIterator: LazySequenceProtocol { }
extension Family {
@inlinable
public func descendRelatives(from root: Entity) -> RelativesIterator {
return RelativesIterator(family: self, root: root)
RelativesIterator(family: self, root: root)
}
public struct RelativesIterator: IteratorProtocol {
@ -161,7 +160,7 @@ extension Family {
}
mutating func aggregateRelativesBreathFirst(_ parent: EntityIdentifier) {
guard let children = nexus.parentChildrenMap[parent] else {
guard let children = nexus.childrenByParentEntity[parent] else {
return
}
children

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,50 +23,48 @@ public struct FamilyTraitSet {
self.setHash = FirebladeECS.hash(combine: [requiresAll, excludesAll])
}
// MARK: - match
@inlinable
public func isMatch(components: Set<ComponentIdentifier>) -> Bool {
return hasAll(components) && hasNone(components)
hasAll(components) && hasNone(components)
}
@inlinable
public func hasAll(_ components: Set<ComponentIdentifier>) -> Bool {
return requiresAll.isSubset(of: components)
requiresAll.isSubset(of: components)
}
@inlinable
public func hasNone(_ components: Set<ComponentIdentifier>) -> Bool {
return excludesAll.isDisjoint(with: components)
excludesAll.isDisjoint(with: components)
}
// MARK: - valid
@inlinable
public static func isValid(requiresAll: Set<ComponentIdentifier>, excludesAll: Set<ComponentIdentifier>) -> Bool {
return !requiresAll.isEmpty &&
!requiresAll.isEmpty &&
requiresAll.isDisjoint(with: excludesAll)
}
}
// MARK: - Equatable
extension FamilyTraitSet: Equatable {
public static func == (lhs: FamilyTraitSet, rhs: FamilyTraitSet) -> Bool {
return lhs.setHash == rhs.setHash
lhs.setHash == rhs.setHash
}
}
// MARK: - Hashable
extension FamilyTraitSet: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(setHash)
}
}
extension FamilyTraitSet: CustomStringConvertible, CustomDebugStringConvertible {
extension FamilyTraitSet: CustomStringConvertible {
@inlinable public var description: String {
return "<FamilyTraitSet [requiresAll:\(requiresAll.description) excludesAll:\(excludesAll.description)]>"
}
@inlinable public var debugDescription: String {
return "<FamilyTraitSet [requiresAll:\(requiresAll.debugDescription) excludesAll: \(excludesAll.debugDescription)]>"
"<FamilyTraitSet [requiresAll:\(requiresAll.description) excludesAll:\(excludesAll.description)]>"
}
}
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))
}
}
// 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 {
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 {
@ -18,14 +18,14 @@ extension Nexus {
}
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) {
let componentId: ComponentIdentifier = component.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 {
delegate?.nexusNonFatalError("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
if componentsByType[componentId] == nil {
componentsByType[componentId] = ManagedContiguousArray<Component>()
componentsByType[componentId] = UnorderedSparseSet<Component>()
}
componentsByType[componentId]?.insert(component, at: entityId.id)
@ -76,13 +76,13 @@ extension Nexus {
@inlinable
public final func get<C>(unsafeComponentFor entityId: EntityIdentifier) -> C where C: Component {
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)
}
@inlinable
public final func get(components entityId: EntityIdentifier) -> Set<ComponentIdentifier>? {
return componentIdsByEntity[entityId]
componentIdsByEntity[entityId]
}
@discardableResult

View File

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

View File

@ -7,7 +7,7 @@
extension Nexus {
public final var numFamilies: Int {
return familyMembersByTraits.keys.count
familyMembersByTraits.keys.count
}
public func canBecomeMember(_ entity: Entity, in traits: FamilyTraitSet) -> Bool {
@ -19,18 +19,18 @@ extension Nexus {
}
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 {
return isMember(entity.identifier, in: family)
isMember(entity.identifier, in: family)
}
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 {
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>()
defer { delegate?.nexusEvent(FamilyCreated(family: traits)) }
update(familyMembership: traits)
}
final func update(familyMembership traits: FamilyTraitSet) {
// FIXME: iterating all entities is costly for many entities
var iter = entityStorage.makeIterator()
while let entity = iter.next() {
update(membership: traits, for: entity.identifier)
while let entityId = iter.next() {
update(membership: traits, for: entityId)
}
}
@ -50,25 +51,26 @@ extension Nexus {
case (true, false):
add(entityWithId: entityId, toFamilyWithTraits: traits)
delegate?.nexusEvent(FamilyMemberAdded(member: entityId, toFamily: traits))
return
case (false, true):
remove(entityWithId: entityId, fromFamilyWithTraits: traits)
delegate?.nexusEvent(FamilyMemberRemoved(member: entityId, from: traits))
return
default:
return
break
}
}
final func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) {
precondition(familyMembersByTraits[traits] != nil)
familyMembersByTraits[traits].unsafelyUnwrapped.insert(entityId, at: entityId.id)
familyMembersByTraits[traits]!.insert(entityId, at: entityId.id)
}
final func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) {
precondition(familyMembersByTraits[traits] != nil)
familyMembersByTraits[traits].unsafelyUnwrapped.remove(at: entityId.id)
familyMembersByTraits[traits]!.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 {
public final func addChild(_ child: Entity, to parent: Entity) -> Bool {
let inserted: Bool
if parentChildrenMap[parent.identifier] == nil {
parentChildrenMap[parent.identifier] = [child.identifier]
if childrenByParentEntity[parent.identifier] == nil {
childrenByParentEntity[parent.identifier] = [child.identifier]
inserted = true
} else {
let (isNewMember, _) = parentChildrenMap[parent.identifier]!.insert(child.identifier)
let (isNewMember, _) = childrenByParentEntity[parent.identifier]!.insert(child.identifier)
inserted = isNewMember
}
if inserted {
@ -22,12 +22,12 @@ extension Nexus {
}
public final func removeChild(_ child: Entity, from parent: Entity) -> Bool {
return removeChild(child.identifier, from: parent.identifier)
removeChild(child.identifier, from: parent.identifier)
}
@discardableResult
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 {
delegate?.nexusEvent(ChildRemoved(parent: parent, child: child))
}
@ -35,11 +35,15 @@ extension Nexus {
}
public final func removeAllChildren(from parent: Entity) {
parentChildrenMap[parent.identifier]?.forEach { removeChild($0, from: parent.identifier) }
return parentChildrenMap[parent.identifier] = nil
self.removeAllChildren(from: parent.identifier)
}
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 {
return parentChildrenMap[entity.identifier]?.count ?? 0
childrenByParentEntity[entity.identifier]?.count ?? 0
}
}

View File

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

View File

@ -29,17 +29,23 @@ public struct Single<A> where A: SingleComponent {
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 {
@inlinable public var component: A {
/// 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.
/// Since components will allways be of reference type (class) we may use unsafeDowncast here for performance reasons.
// 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.
// Since components will allways be of reference type (class) we may use unsafeDowncast here for performance reasons.
return nexus.get(unsafeComponentFor: entityId)
}
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.
//
open class UnorderedSparseSet<Element> {
public struct UnorderedSparseSet<Element> {
public typealias Index = Int
public typealias Key = Int
@ -18,20 +18,20 @@ open class UnorderedSparseSet<Element> {
@usableFromInline var sparse: [Index: Key]
public init() {
sparse = [Index: Key]()
dense = ContiguousArray<Entry>()
self.init(sparse: [:], dense: [])
}
deinit {
removeAll()
init(sparse: [Index: Key], dense: ContiguousArray<Entry>) {
self.sparse = sparse
self.dense = dense
}
public var count: Int { return dense.count }
public var isEmpty: Bool { return dense.isEmpty }
public var count: Int { dense.count }
public var isEmpty: Bool { dense.isEmpty }
@inlinable
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).
@ -42,7 +42,7 @@ open class UnorderedSparseSet<Element> {
/// - key: the key
/// - Returns: true if new, false if replaced.
@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) {
dense[denseIndex] = Entry(key: key, element: element)
return false
@ -69,7 +69,7 @@ open class UnorderedSparseSet<Element> {
@inlinable
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).
@ -77,7 +77,7 @@ open class UnorderedSparseSet<Element> {
/// - Parameter key: the key
/// - Returns: removed value or nil if key not found.
@discardableResult
public func remove(at key: Key) -> Entry? {
public mutating func remove(at key: Key) -> Entry? {
guard let (denseIndex, _) = find(at: key) else {
return nil
}
@ -92,22 +92,17 @@ open class UnorderedSparseSet<Element> {
}
@inlinable
public func removeAll(keepingCapacity: Bool = false) {
public mutating func removeAll(keepingCapacity: Bool = false) {
sparse.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).
/// The removed element is replaced with the last element of the set.
///
/// - Parameter denseIndex: the dense index
/// - 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)
return dense.removeLast()
}
@ -128,7 +123,7 @@ open class UnorderedSparseSet<Element> {
@inlinable
public subscript(position: Index) -> Element {
get {
return get(unsafeAt: position)
get(unsafeAt: position)
}
set(newValue) {
@ -137,30 +132,42 @@ open class UnorderedSparseSet<Element> {
}
@inlinable public var first: Element? {
return dense.first?.element
dense.first?.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: Equatable where Element: Equatable {
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
public struct UnorderedSparseSetIterator<Element>: 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? {
return iterator.next()?.element
}
}
// MARK: - Codable
extension UnorderedSparseSet.Entry: Codable where Element: Codable { }
extension UnorderedSparseSet: Codable where Element: Codable { }

View File

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

View File

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

View File

@ -8,7 +8,16 @@
import FirebladeECS
import XCTest
#if DEBUG
let isDebug: Bool = true
#else
let isDebug: Bool = false
#endif
class HashingPerformanceTests: XCTestCase {
/// release: 0.726 sec
/// debug: 3.179 sec
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 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() {
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])
@ -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()
}
/// release: 0.011 sec
/// debug: 0.017 sec
func testMeasureTraitMatching() {
let a = nexus.createEntity()
a.assign(Position(x: 1, y: 2))
@ -48,6 +50,8 @@ class TypedFamilyPerformanceTests: XCTestCase {
}
}
/// release: 0.001 sec
/// debug: 0.008 sec
func testPerformanceTypedFamilyEntities() {
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
@ -71,6 +75,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10)
}
/// debug: 0.004 sec
func testPerformanceArray() {
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)
}
/// release: 0.003 sec
/// debug: 0.010 sec
func testPerformanceTypedFamilyOneComponent() {
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
@ -109,6 +116,8 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10)
}
/// release: 0.004 sec
/// debug: 0.016 sec
func testPerformanceTypedFamilyEntityOneComponent() {
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
@ -133,6 +142,8 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10)
}
/// release: 0.005 sec
/// debug: 0.016 sec
func testPerformanceTypedFamilyTwoComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self)
@ -156,6 +167,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10)
}
/// release: 0.006 sec
func testPerformanceTypedFamilyEntityTwoComponents() {
let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self)
@ -181,6 +193,7 @@ class TypedFamilyPerformanceTests: XCTestCase {
XCTAssertEqual(loopCount, family.count * 10)
}
/// release: 0.007 sec
func testPerformanceTypedFamilyThreeComponents() {
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)
}
/// release: 0.008 sec
func testPerformanceTypedFamilyEntityThreeComponents() {
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)
}
/// release: 0.009 sec
func testPerformanceTypedFamilyFourComponents() {
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)
}
/// release: 0.010 sec
func testPerformanceTypedFamilyEntityFourComponents() {
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)
}
/// release: 0.012 sec
func testPerformanceTypedFamilyFiveComponents() {
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)
}
/// release: 0.012 sec
func testPerformanceTypedFamilyEntityFiveComponents() {
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)
import XCTest
extension ComponentTests {
extension ComponentIdentifierTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentTests = [
static let __allTests__ComponentIdentifierTests = [
("testMeasureComponentIdentifier", testMeasureComponentIdentifier),
("testMeasureStaticComponentIdentifier", testMeasureStaticComponentIdentifier)
]
@ -16,8 +16,25 @@ extension HashingPerformanceTests {
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__HashingPerformanceTests = [
("testMeasureBernsteinDjb2", testMeasureBernsteinDjb2),
("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] {
return [
testCase(ComponentTests.__allTests__ComponentTests),
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
testCase(HashingPerformanceTests.__allTests__HashingPerformanceTests),
testCase(TypeIdentifierPerformanceTests.__allTests__TypeIdentifierPerformanceTests),
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
class EmptyComponent: Component { }
class EmptyComponent: Component {
}
class Name: Component {
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.
//
import FirebladeECS
#if DEBUG
@testable import FirebladeECS
import XCTest
class EntityTests: XCTestCase {
@ -21,9 +22,5 @@ class EntityTests: XCTestCase {
XCTAssertEqual(max, EntityIdentifier.invalid)
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.
//
#if DEBUG
@testable import FirebladeECS
import XCTest
@ -31,7 +32,6 @@ class FamilyTests: XCTestCase {
let family = nexus.family(requires: Position.self,
excludesAll: Name.self)
XCTAssertEqual(family.nexus, self.nexus)
XCTAssertTrue(family.nexus === self.nexus)
XCTAssertEqual(nexus.numFamilies, 1)
XCTAssertEqual(nexus.numComponents, 0)
@ -73,11 +73,11 @@ class FamilyTests: XCTestCase {
XCTAssertEqual(nexus.numComponents, 1)
XCTAssertEqual(nexus.numEntities, 1)
entity.remove(Position.self)
XCTAssertEqual(nexus.numFamilies, 1)
XCTAssertEqual(nexus.numFamilies, 0)
XCTAssertEqual(nexus.numComponents, 0)
XCTAssertEqual(nexus.numEntities, 1)
nexus.destroy(entity: entity)
XCTAssertEqual(nexus.numFamilies, 1)
XCTAssertEqual(nexus.numFamilies, 0)
XCTAssertEqual(nexus.numComponents, 0)
XCTAssertEqual(nexus.numEntities, 0)
}
@ -132,7 +132,7 @@ class FamilyTests: XCTestCase {
entity.remove(velocity)
}
XCTAssertEqual(familyA.count, 10)
XCTAssertEqual(familyA.count, 0)
XCTAssertEqual(familyB.count, 0)
}
@ -186,3 +186,4 @@ class FamilyTests: XCTestCase {
XCTAssertEqual(family.memberIds.count, count + (count / 2))
}
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

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