Merge branch 'feature/codable' into 'develop'
v0.11 | Codable See merge request fireblade/ecs!5
This commit is contained in:
commit
0af7cc0bfe
|
|
@ -46,7 +46,6 @@ fastlane/test_output
|
|||
Gemfile*
|
||||
Icon
|
||||
Network Trash Folder
|
||||
Package.resolved
|
||||
Packages
|
||||
playground.xcworkspace
|
||||
Temporary Items
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
image: swift:5.0
|
||||
image: swift:5.2.2
|
||||
|
||||
#before_script:
|
||||
#- eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
5.0.3
|
||||
5.2.2
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
12
.travis.yml
12
.travis.yml
|
|
@ -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
|
||||
|
||||
49
Makefile
49
Makefile
|
|
@ -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
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# Fireblade ECS (Entity-Component System)
|
||||
[](https://travis-ci.com/fireblade-engine/ecs)
|
||||
[](LICENSE)
|
||||
[](https://swift.org/download)
|
||||
[](https://swift.org/download)
|
||||
[](#)
|
||||
[](#)
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)]>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
import FirebladeECS
|
||||
|
||||
class EmptyComponent: Component { }
|
||||
class EmptyComponent: Component {
|
||||
|
||||
}
|
||||
|
||||
class Name: Component {
|
||||
var name: String
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in New Issue