Fix hashing
This commit is contained in:
parent
ca36926975
commit
136905e43b
|
|
@ -15,7 +15,11 @@ extension ComponentIdentifier {
|
||||||
/// - Parameter entityIdx: entity index
|
/// - Parameter entityIdx: entity index
|
||||||
/// - Returns: combinded entity component hash
|
/// - Returns: combinded entity component hash
|
||||||
func hashValue(using entityIdx: EntityIndex) -> EntityComponentHash {
|
func hashValue(using entityIdx: EntityIndex) -> EntityComponentHash {
|
||||||
return self.hashValue ^ entityIdx
|
return hashValue(using: entityIdx.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashValue(using entityId: EntityIdentifier) -> EntityComponentHash {
|
||||||
|
return EntityComponentHash.compose(entityId: entityId, componentTypeHash: hashValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
//
|
|
||||||
// EntityHub2.swift
|
|
||||||
// FirebladeECS
|
|
||||||
//
|
|
||||||
// Created by Christian Treffs on 11.10.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
class EntityHub2 {
|
|
||||||
/*
|
|
||||||
fileprivate typealias CompType = ComponentIdentifier
|
|
||||||
fileprivate typealias CompIdxInCompArrayForType = Int
|
|
||||||
fileprivate typealias ComponentArray = ContiguousArray<Component>
|
|
||||||
fileprivate typealias EntityID = Int // aka EntityIdentifier
|
|
||||||
|
|
||||||
fileprivate var componentsByType: [CompType: ComponentArray] = [:]
|
|
||||||
fileprivate var entitiesById: [EntityID: Entity] = [:]
|
|
||||||
fileprivate var componentLookupByEntityId: [EntityID: [CompType:CompIdxInCompArrayForType]] = [:]
|
|
||||||
fileprivate var entityIdLookupByCompType: [CompType: [CompIdxInCompArrayForType:EntityID]] = [:]
|
|
||||||
|
|
||||||
|
|
||||||
fileprivate func query(_ compTypes: Component.Type...) -> [Entity] {
|
|
||||||
let types = compTypes.map{ $0.identifier }
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func queryTypes(_ types: [CompType]) -> [Entity] {
|
|
||||||
var types = types
|
|
||||||
|
|
||||||
var minSize: Int = Int.max
|
|
||||||
var minIndex: Int = 0
|
|
||||||
var index: Int = 0
|
|
||||||
|
|
||||||
for t in types {
|
|
||||||
guard let compArray = componentsByType[t] else {
|
|
||||||
fatalError("querying for non existing type \(t.type)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let size: Int = compArray.count
|
|
||||||
if size < minSize {
|
|
||||||
minSize = size
|
|
||||||
minIndex = index
|
|
||||||
}
|
|
||||||
index += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
let minType: CompType = types[minIndex]
|
|
||||||
|
|
||||||
if types.count >= 2 && minIndex != (types.count - 1) {
|
|
||||||
types.swapAt(minIndex, (types.count-1))
|
|
||||||
}
|
|
||||||
types.removeLast()
|
|
||||||
|
|
||||||
return iterate(minType, types)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func iterate(_ minType: CompType, _ types: [CompType]) -> [Entity] {
|
|
||||||
var entitiesWithTypes: [Entity] = []
|
|
||||||
|
|
||||||
guard let compArray = componentsByType[minType] else {
|
|
||||||
fatalError("iterating non existing type \(minType.type)")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i: CompIdxInCompArrayForType in 0..<compArray.count {
|
|
||||||
|
|
||||||
let component = compArray[i]
|
|
||||||
let compType = component.identifier
|
|
||||||
|
|
||||||
guard let ownerId: EntityID = entityIdLookupByCompType[compType]?[i]else {
|
|
||||||
fatalError("could not find owner id")
|
|
||||||
}
|
|
||||||
|
|
||||||
if has(allTypes: ownerId, types) {
|
|
||||||
guard let entity = entitiesById[ownerId] else {
|
|
||||||
fatalError("could not find entity")
|
|
||||||
}
|
|
||||||
entitiesWithTypes.append(entity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entitiesWithTypes
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func has(allTypes entityId: EntityID, _ types: [CompType]) -> Bool {
|
|
||||||
guard let entityTypes = componentLookupByEntityId[entityId]?.keys else { return false }
|
|
||||||
|
|
||||||
for requiredType: CompType in types {
|
|
||||||
if !entityTypes.contains(requiredType) { return false }
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
@ -7,20 +7,23 @@
|
||||||
|
|
||||||
// MARK: Unique Entity Index
|
// MARK: Unique Entity Index
|
||||||
|
|
||||||
public typealias EntityIdentifier = UInt64 // provides 18446744073709551615 unique identifiers
|
public typealias EntityIdentifier = UInt32 // provides 4294967295 unique identifiers
|
||||||
public typealias EntityIndex = Int
|
public typealias EntityIndex = Int
|
||||||
public typealias EntityReuseCount = UInt32
|
|
||||||
|
|
||||||
public extension EntityIdentifier {
|
public extension EntityIdentifier {
|
||||||
static let invalid: EntityIdentifier = EntityIdentifier.max
|
static let invalid: EntityIdentifier = EntityIdentifier.max
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension EntityIdentifier {
|
public extension EntityIdentifier {
|
||||||
public var index: EntityIndex { return EntityIndex(self & 0xffffffff) } // shifts entity identifier by UInt32.max
|
public var index: EntityIndex {
|
||||||
|
return EntityIndex(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension EntityIndex {
|
public extension EntityIndex {
|
||||||
public var identifier: EntityIdentifier { return EntityIdentifier(self & 0xffffffff ) } // shifts entity identifier by UInt32.max
|
public var identifier: EntityIdentifier {
|
||||||
|
return EntityIdentifier(truncatingIfNeeded: self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Unique Entity Identifiable
|
// MARK: Unique Entity Identifiable
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Hashing.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 16.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
extension EntityComponentHash {
|
||||||
|
|
||||||
|
static func compose(entityId: EntityIdentifier, componentTypeHash: ComponentTypeHash) -> EntityComponentHash {
|
||||||
|
let entityIdSwapped: UInt = UInt(entityId).byteSwapped // needs to be 64 bit
|
||||||
|
let componentTypeHashUInt: UInt = UInt(bitPattern: componentTypeHash)
|
||||||
|
let hashUInt: UInt = componentTypeHashUInt ^ entityIdSwapped
|
||||||
|
return Int(bitPattern: hashUInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func decompose(_ hash: EntityComponentHash, with entityId: EntityIdentifier) -> ComponentTypeHash {
|
||||||
|
let entityIdSwapped: UInt = UInt(entityId).byteSwapped
|
||||||
|
let entityIdSwappedInt = Int(bitPattern: entityIdSwapped)
|
||||||
|
return hash ^ entityIdSwappedInt
|
||||||
|
}
|
||||||
|
|
||||||
|
static func decompose(_ hash: EntityComponentHash, with componentTypeHash: ComponentTypeHash) -> EntityIdentifier {
|
||||||
|
let entityId: Int = (hash ^ componentTypeHash).byteSwapped
|
||||||
|
return EntityIdentifier(truncatingIfNeeded: entityId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ extension Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func has(entity entityId: EntityIdentifier) -> Bool {
|
public func has(entity entityId: EntityIdentifier) -> Bool {
|
||||||
return isValid(entity: entityId) // TODO: reuse free index
|
return isValid(entity: entityId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ public extension ComponentIndex {
|
||||||
static let invalid: ComponentIndex = Int.min
|
static let invalid: ComponentIndex = Int.min
|
||||||
}
|
}
|
||||||
public typealias ComponentIdsByEntityIndex = Int
|
public typealias ComponentIdsByEntityIndex = Int
|
||||||
|
public typealias ComponentTypeHash = Int // component object identifier hash value
|
||||||
|
|
||||||
public typealias UniformComponents = ContiguousArray<Component>
|
public typealias UniformComponents = ContiguousArray<Component>
|
||||||
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>
|
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
//
|
||||||
|
// HashingTests.swift
|
||||||
|
// FirebladeECSTests
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 16.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Darwin
|
||||||
|
import XCTest
|
||||||
|
@testable import FirebladeECS
|
||||||
|
|
||||||
|
class HashingTests: XCTestCase {
|
||||||
|
|
||||||
|
func test() {
|
||||||
|
|
||||||
|
var hashSet: Set<Int> = Set<Int>()
|
||||||
|
|
||||||
|
var range: CountableRange<EntityIdentifier> = 0 ..< 1_000_000
|
||||||
|
|
||||||
|
let maxComponents: Int = 1000
|
||||||
|
let components: [Int] = (0..<maxComponents).map { _ in makeComponent() }
|
||||||
|
|
||||||
|
var index: Int = 0
|
||||||
|
while let eId: EntityIdentifier = range.popLast() {
|
||||||
|
|
||||||
|
let entityId: EntityIdentifier = eId
|
||||||
|
let c = (index % maxComponents)
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
let cH: ComponentTypeHash = components[c]
|
||||||
|
|
||||||
|
let h: Int = EntityComponentHash.compose(entityId: entityId, componentTypeHash: cH)
|
||||||
|
|
||||||
|
let (collisionFree, _) = hashSet.insert(h)
|
||||||
|
XCTAssert(collisionFree)
|
||||||
|
|
||||||
|
XCTAssert(EntityComponentHash.decompose(h, with: cH) == entityId)
|
||||||
|
XCTAssert(EntityComponentHash.decompose(h, with: entityId) == cH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - helper
|
||||||
|
extension HashingTests {
|
||||||
|
|
||||||
|
func makeComponent() -> Int {
|
||||||
|
let upperBound: Int = 44
|
||||||
|
let high = UInt(arc4random()) << UInt(upperBound)
|
||||||
|
let low = UInt(arc4random())
|
||||||
|
assert(high.leadingZeroBitCount < 64-upperBound)
|
||||||
|
assert(high.trailingZeroBitCount >= upperBound)
|
||||||
|
assert(low.leadingZeroBitCount >= 32)
|
||||||
|
assert(low.trailingZeroBitCount <= 32)
|
||||||
|
let rand: UInt = high | low
|
||||||
|
let cH = Int(bitPattern: rand)
|
||||||
|
return cH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,13 +21,13 @@ class NexusTests: XCTestCase {
|
||||||
|
|
||||||
func testEntityIdentifierAndIndex() {
|
func testEntityIdentifierAndIndex() {
|
||||||
|
|
||||||
let min: EntityIndex = EntityIdentifier(UInt64.min).index
|
let min: EntityIndex = EntityIdentifier(EntityIdentifier.min).index
|
||||||
XCTAssert(EntityIndex(min).identifier == min)
|
XCTAssert(EntityIndex(min).identifier == min)
|
||||||
|
|
||||||
let rand: EntityIndex = EntityIdentifier(UInt64(arc4random())).index
|
let rand: EntityIndex = EntityIdentifier(EntityIdentifier(arc4random())).index
|
||||||
XCTAssert(EntityIndex(rand).identifier == rand)
|
XCTAssert(EntityIndex(rand).identifier == rand)
|
||||||
|
|
||||||
let max: EntityIndex = EntityIdentifier(UInt64.max).index
|
let max: EntityIndex = EntityIdentifier(EntityIdentifier.max).index
|
||||||
XCTAssert(EntityIndex(max).identifier == max)
|
XCTAssert(EntityIndex(max).identifier == max)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue