Fix hashing

This commit is contained in:
Christian Treffs 2017-10-16 23:31:31 +02:00
parent ca36926975
commit 136905e43b
8 changed files with 102 additions and 105 deletions

View File

@ -15,7 +15,11 @@ extension ComponentIdentifier {
/// - Parameter entityIdx: entity index
/// - Returns: combinded entity component hash
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)
}
}

View File

@ -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
}
*/
}

View File

@ -7,20 +7,23 @@
// 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 EntityReuseCount = UInt32
public extension EntityIdentifier {
static let invalid: EntityIdentifier = EntityIdentifier.max
}
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 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

View File

@ -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)
}
}

View File

@ -48,7 +48,7 @@ extension Nexus {
}
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? {

View File

@ -12,6 +12,7 @@ public extension ComponentIndex {
static let invalid: ComponentIndex = Int.min
}
public typealias ComponentIdsByEntityIndex = Int
public typealias ComponentTypeHash = Int // component object identifier hash value
public typealias UniformComponents = ContiguousArray<Component>
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>

View File

@ -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
}
}

View File

@ -21,13 +21,13 @@ class NexusTests: XCTestCase {
func testEntityIdentifierAndIndex() {
let min: EntityIndex = EntityIdentifier(UInt64.min).index
let min: EntityIndex = EntityIdentifier(EntityIdentifier.min).index
XCTAssert(EntityIndex(min).identifier == min)
let rand: EntityIndex = EntityIdentifier(UInt64(arc4random())).index
let rand: EntityIndex = EntityIdentifier(EntityIdentifier(arc4random())).index
XCTAssert(EntityIndex(rand).identifier == rand)
let max: EntityIndex = EntityIdentifier(UInt64.max).index
let max: EntityIndex = EntityIdentifier(EntityIdentifier.max).index
XCTAssert(EntityIndex(max).identifier == max)
}