Fix hashing
This commit is contained in:
parent
ca36926975
commit
136905e43b
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
return isValid(entity: entityId) // TODO: reuse free index
|
||||
return isValid(entity: entityId)
|
||||
}
|
||||
|
||||
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue