This commit is contained in:
Christian Treffs 2024-10-21 13:54:11 +02:00
parent 041440c40d
commit cae5c71af9
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
5 changed files with 119 additions and 114 deletions

View File

@ -23,12 +23,12 @@ extension ComponentIdentifier {
} }
typealias StableId = UInt64 typealias StableId = UInt64
internal static func makeStableTypeHash(component: Component) -> StableId { static func makeStableTypeHash(component: Component) -> StableId {
let componentTypeString = String(describing: type(of: component)) let componentTypeString = String(describing: type(of: component))
return StringHashing.singer_djb2(componentTypeString) return StringHashing.singer_djb2(componentTypeString)
} }
internal static func makeStableInstanceHash(component: Component, entityId: EntityIdentifier) -> StableId { static func makeStableInstanceHash(component: Component, entityId: EntityIdentifier) -> StableId {
let componentTypeString = String(describing: type(of: component)) + String(entityId.id) let componentTypeString = String(describing: type(of: component)) + String(entityId.id)
return StringHashing.singer_djb2(componentTypeString) return StringHashing.singer_djb2(componentTypeString)
} }

View File

@ -101,4 +101,5 @@ public struct LinearIncrementingEntityIdGenerator: EntityIdentifierGenerator {
storage.markUnused(entityId: entityId) storage.markUnused(entityId: entityId)
} }
} }
extension LinearIncrementingEntityIdGenerator.Storage: Codable { }
extension LinearIncrementingEntityIdGenerator.Storage: Codable {}

View File

@ -7,7 +7,7 @@
extension Nexus: Encodable { extension Nexus: Encodable {
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
let serializedNexus = try self.serialize() let serializedNexus = try serialize()
var container = encoder.singleValueContainer() var container = encoder.singleValueContainer()
try container.encode(serializedNexus) try container.encode(serializedNexus)
} }

View File

@ -6,119 +6,122 @@
// //
#if canImport(Foundation) #if canImport(Foundation)
import struct Foundation.Data import struct Foundation.Data
extension Nexus { extension Nexus {
final func serialize() throws -> SNexus { final func serialize() throws -> SNexus {
var componentInstances: [ComponentIdentifier.StableId: SComponent<SNexus>] = [:] var componentInstances: [ComponentIdentifier.StableId: SComponent<SNexus>] = [:]
var entityComponentsMap: [EntityIdentifier: Set<ComponentIdentifier.StableId>] = [:] var entityComponentsMap: [EntityIdentifier: Set<ComponentIdentifier.StableId>] = [:]
for entitId in self.componentIdsByEntity.keys { for entitId in componentIdsByEntity.keys {
entityComponentsMap[entitId] = [] entityComponentsMap[entitId] = []
let componentIds = self.get(components: entitId) ?? [] let componentIds = self.get(components: entitId) ?? []
for componentId in componentIds { for componentId in componentIds {
let component = self.get(unsafe: componentId, for: entitId) let component = self.get(unsafe: componentId, for: entitId)
let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId)
componentInstances[componentStableInstanceHash] = SComponent.component(component) componentInstances[componentStableInstanceHash] = SComponent.component(component)
entityComponentsMap[entitId]?.insert(componentStableInstanceHash) entityComponentsMap[entitId]?.insert(componentStableInstanceHash)
}
}
return SNexus(version: version,
entities: entityComponentsMap,
components: componentInstances)
}
final func deserialize(from sNexus: SNexus, into nexus: Nexus) throws {
for freeId in sNexus.entities.map(\.key).reversed() {
nexus.entityIdGenerator.markUnused(entityId: freeId)
}
for componentSet in sNexus.entities.values {
let entity = self.createEntity()
for sCompId in componentSet {
guard let sComp = sNexus.components[sCompId] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Could not find component instance for \(sCompId)."))
} }
}
switch sComp { return SNexus(version: version,
case let .component(comp): entities: entityComponentsMap,
entity.assign(comp) components: componentInstances)
}
final func deserialize(from sNexus: SNexus, into nexus: Nexus) throws {
for freeId in sNexus.entities.map(\.key).reversed() {
nexus.entityIdGenerator.markUnused(entityId: freeId)
}
for componentSet in sNexus.entities.values {
let entity = createEntity()
for sCompId in componentSet {
guard let sComp = sNexus.components[sCompId] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Could not find component instance for \(sCompId)."))
}
switch sComp {
case let .component(comp):
entity.assign(comp)
}
} }
} }
} }
} }
}
extension EntityIdentifier: Encodable { extension EntityIdentifier: Encodable {
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer() var container = encoder.singleValueContainer()
try container.encode(id) try container.encode(id)
}
}
extension EntityIdentifier: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let id = try container.decode(UInt32.self)
self.init(id)
}
}
internal struct SNexus {
let version: Version
let entities: [EntityIdentifier: Set<ComponentIdentifier.StableId>]
let components: [ComponentIdentifier.StableId: SComponent<SNexus>]
}
extension SNexus: Encodable { }
extension SNexus: Decodable { }
protocol ComponentEncoding {
static func encode(component: Component, to encoder: Encoder) throws
}
protocol ComponentDecoding {
static func decode(from decoder: Decoder) throws -> Component
}
typealias ComponentCodable = ComponentEncoding & ComponentDecoding
extension SNexus: ComponentEncoding {
static func encode(component: Component, to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let bytes = withUnsafeBytes(of: component) {
Data(bytes: $0.baseAddress!, count: MemoryLayout.stride(ofValue: component))
}
try container.encode(bytes)
}
}
extension SNexus: ComponentDecoding {
static func decode(from decoder: Decoder) throws -> Component {
let container = try decoder.singleValueContainer()
let instanceData = try container.decode(Data.self)
return instanceData.withUnsafeBytes {
$0.baseAddress!.load(as: Component.self)
} }
} }
}
enum SComponent<CodingStrategy: ComponentCodable> { extension EntityIdentifier: Decodable {
case component(Component) public init(from decoder: Decoder) throws {
} let container = try decoder.singleValueContainer()
let id = try container.decode(UInt32.self)
extension SComponent: Encodable { self.init(id)
public func encode(to encoder: Encoder) throws {
switch self {
case let .component(comp):
try CodingStrategy.encode(component: comp, to: encoder)
} }
} }
}
extension SComponent: Decodable { struct SNexus {
public init(from decoder: Decoder) throws { let version: Version
self = .component(try CodingStrategy.decode(from: decoder)) let entities: [EntityIdentifier: Set<ComponentIdentifier.StableId>]
let components: [ComponentIdentifier.StableId: SComponent<SNexus>]
}
extension SNexus: Encodable {}
extension SNexus: Decodable {}
protocol ComponentEncoding {
static func encode(component: Component, to encoder: Encoder) throws
}
protocol ComponentDecoding {
static func decode(from decoder: Decoder) throws -> Component
}
typealias ComponentCodable = ComponentDecoding & ComponentEncoding
extension SNexus: ComponentEncoding {
static func encode(component: Component, to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let bytes = withUnsafeBytes(of: component) {
Data(bytes: $0.baseAddress!, count: MemoryLayout.stride(ofValue: component))
}
try container.encode(bytes)
}
}
extension SNexus: ComponentDecoding {
static func decode(from decoder: Decoder) throws -> Component {
let container = try decoder.singleValueContainer()
let instanceData = try container.decode(Data.self)
return instanceData.withUnsafeBytes {
$0.baseAddress!.load(as: Component.self)
}
}
}
enum SComponent<CodingStrategy: ComponentCodable> {
case component(Component)
}
extension SComponent: Encodable {
public func encode(to encoder: Encoder) throws {
switch self {
case let .component(comp):
try CodingStrategy.encode(component: comp, to: encoder)
}
}
}
extension SComponent: Decodable {
public init(from decoder: Decoder) throws {
self = try .component(CodingStrategy.decode(from: decoder))
}
} }
}
#endif #endif

View File

@ -45,22 +45,24 @@ public struct Version {
guard requiredComponents.count == 3 else { return nil } guard requiredComponents.count == 3 else { return nil }
self.major = requiredComponents[0] major = requiredComponents[0]
self.minor = requiredComponents[1] minor = requiredComponents[1]
self.patch = requiredComponents[2] patch = requiredComponents[2]
func identifiers(start: String.Index?, end: String.Index) -> [String] { func identifiers(start: String.Index?, end: String.Index) -> [String] {
guard let start = start else { return [] } guard let start else { return [] }
let identifiers = versionString[versionString.index(after: start)..<end] let identifiers = versionString[versionString.index(after: start) ..< end]
return identifiers.split(separator: ".").map(String.init) return identifiers.split(separator: ".").map(String.init)
} }
self.prereleaseIdentifiers = identifiers( prereleaseIdentifiers = identifiers(
start: prereleaseStartIndex, start: prereleaseStartIndex,
end: metadataStartIndex ?? versionString.endIndex) end: metadataStartIndex ?? versionString.endIndex
self.buildMetadataIdentifiers = identifiers( )
buildMetadataIdentifiers = identifiers(
start: metadataStartIndex, start: metadataStartIndex,
end: versionString.endIndex) end: versionString.endIndex
)
} }
public var versionString: String { public var versionString: String {
@ -75,7 +77,7 @@ public struct Version {
} }
} }
extension Version: Equatable { } extension Version: Equatable {}
extension Version: Comparable { extension Version: Comparable {
func isEqualWithoutPrerelease(_ other: Version) -> Bool { func isEqualWithoutPrerelease(_ other: Version) -> Bool {
major == other.major && minor == other.minor && patch == other.patch major == other.major && minor == other.minor && patch == other.patch
@ -89,11 +91,11 @@ extension Version: Comparable {
return lhsComparators.lexicographicallyPrecedes(rhsComparators) return lhsComparators.lexicographicallyPrecedes(rhsComparators)
} }
guard lhs.prereleaseIdentifiers.count > 0 else { guard !lhs.prereleaseIdentifiers.isEmpty else {
return false // Non-prerelease lhs >= potentially prerelease rhs return false // Non-prerelease lhs >= potentially prerelease rhs
} }
guard rhs.prereleaseIdentifiers.count > 0 else { guard !rhs.prereleaseIdentifiers.isEmpty else {
return true // Prerelease lhs < non-prerelease rhs return true // Prerelease lhs < non-prerelease rhs
} }
@ -111,7 +113,6 @@ extension Version: Comparable {
case let (string1 as String, string2 as String): return string1 < string2 case let (string1 as String, string2 as String): return string1 < string2
case (is Int, is String): return true // Int prereleases < String prereleases case (is Int, is String): return true // Int prereleases < String prereleases
case (is String, is Int): return false case (is String, is Int): return false
default: default:
return false return false
} }