Rework component identifier handling
This commit is contained in:
parent
344b0465dd
commit
81cbb0f2b4
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue