Rework component identifier handling

This commit is contained in:
Christian Treffs 2020-04-30 19:53:38 +02:00
parent 344b0465dd
commit 81cbb0f2b4
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
4 changed files with 81 additions and 47 deletions

View File

@ -7,12 +7,17 @@
/// **Component**
///
/// A component represents the raw data for one aspect of an object.
public protocol Component: class, Codable {
/// 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 {
@inlinable public var identifier: ComponentIdentifier { return Self.identifier }
public static var identifier: ComponentIdentifier { ComponentIdentifier(Self.self) }
@inline(__always)
public var identifier: ComponentIdentifier { Self.identifier }
}

View File

@ -6,21 +6,19 @@
//
/// Identifies a component by it's meta type
public struct ComponentIdentifier: Identifiable {
public let id: String
public struct ComponentIdentifier {
@usableFromInline
typealias Hash = Int
@usableFromInline
typealias StableId = UInt
@usableFromInline let hash: Hash
}
public init<T>(_ componentType: T.Type) where T: Component {
defer { Nexus.register(component: T.self, using: self) }
self.id = String(reflecting: componentType)
extension ComponentIdentifier {
@usableFromInline init<C>(_ componentType: C.Type) where C: Component {
self.hash = Nexus.makeOrGetComponentId(componentType)
}
}
extension ComponentIdentifier: Equatable { }
extension ComponentIdentifier: Hashable { }
extension ComponentIdentifier: Codable { }
extension ComponentIdentifier: Comparable {
public static func < (lhs: ComponentIdentifier, rhs: ComponentIdentifier) -> Bool {
return lhs.id < rhs.id
}
}

View File

@ -83,3 +83,52 @@ extension EntityComponentHash {
return EntityIdentifier(UInt32(truncatingIfNeeded: entityId))
}
}
// MARK: - string hashing
/// https://stackoverflow.com/a/52440609
public enum StringHashing {
/// *Waren Singer djb2*
///
/// <https://stackoverflow.com/a/43149500>
public static func singer_djb2(_ utf8String: String) -> UInt {
var hash = UInt(5381)
var iter = utf8String.unicodeScalars.makeIterator()
while let char = iter.next() {
hash = 127 * (hash & 0x00ffffffffffffff) + UInt(char.value)
}
return hash
}
/// *Dan Bernstein djb2*
///
/// This algorithm (k=33) was first reported by dan bernstein many years ago in comp.lang.c.
/// Another version of this algorithm (now favored by bernstein) uses xor: hash(i) = hash(i - 1) * 33 ^ str[i];
/// The magic of number 33 (why it works better than many other constants, prime or not) has never been adequately explained.
///
/// <http://www.cse.yorku.ca/~oz/hash.html>
public static func bernstein_djb2(_ string: String) -> UInt {
var hash: UInt = 5381
var iter = string.unicodeScalars.makeIterator()
while let char = iter.next() {
hash = (hash << 5) &+ hash &+ UInt(char.value)
//hash = ((hash << 5) + hash) + UInt(c.value)
}
return hash
}
/// *sdbm*
///
/// This algorithm was created for sdbm (a public-domain reimplementation of ndbm) database library.
/// It was found to do well in scrambling bits, causing better distribution of the keys and fewer splits.
/// It also happens to be a good general hashing function with good distribution.
///
/// <http://www.cse.yorku.ca/~oz/hash.html>
public static func sdbm(_ string: String) -> UInt {
var hash: UInt = 0
var iter = string.unicodeScalars.makeIterator()
while let char = iter.next() {
hash = (UInt(char.value) &+ (hash << 6) &+ (hash << 16))
}
return hash
}
}

View File

@ -6,9 +6,6 @@
//
public final class Nexus {
/// Static version string.
public static let version: String = "1.0.0"
/// Main entity storage.
/// Entities are tightly packed by EntityIdentifier.
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier>
@ -74,45 +71,30 @@ public final class Nexus {
}
public static var knownUniqueComponentTypes: Set<ComponentIdentifier> {
return Set<ComponentIdentifier>(componentDecoderMap.keys)
}
internal static var componentDecoderMap: [ComponentIdentifier: (Decoder) throws -> Component] = [:]
/// Register a component type uniquely with the Nexus implementation.
/// - Parameters:
/// - componentType: The component meta type.
/// - identifier: The unique identifier.
internal static func register<C>(component componentType: C.Type, using identifier: ComponentIdentifier) where C: Component {
precondition(componentDecoderMap[identifier] == nil, "Component type collision: \(identifier) already in use.")
componentDecoderMap[identifier] = { try C(from: $0) }
Set<ComponentIdentifier>(stableComponentIdentifierMap.keys.map { ComponentIdentifier(hash: $0) })
}
}
// MARK: - Errors
// MARK: - centralized component identifier mapping
extension Nexus {
public enum Error: Swift.Error {
case versionMismatch(required: String, provided: String)
}
}
internal static var stableComponentIdentifierMap: [ComponentIdentifier.Hash: ComponentIdentifier.StableId] = [:]
// 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.childrenByParentEntity == rhs.childrenByParentEntity
// NOTE: components are not equatable (yet)
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)>"
}
}