From 67b2dbfb4f58c0703d2ea74ba9c342b0bb785648 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 10:19:58 +0200 Subject: [PATCH 01/23] WIP: encoding --- Tests/FirebladeECSTests/Base.swift | 6 +- .../SerializationTests.swift | 141 ++++++++++++++++++ 2 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 Tests/FirebladeECSTests/SerializationTests.swift diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index ded9b861..b49a56a0 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -11,14 +11,14 @@ class EmptyComponent: Component { } -class Name: Component { +class Name: Component, Encodable { var name: String init(name: String) { self.name = name } } -class Position: Component { +class Position: Component, Encodable { var x: Int var y: Int init(x: Int, y: Int) { @@ -27,7 +27,7 @@ class Position: Component { } } -class Velocity: Component { +class Velocity: Component, Encodable { var a: Float init(a: Float) { self.a = a diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift new file mode 100644 index 00000000..cbaa3ed2 --- /dev/null +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -0,0 +1,141 @@ +// +// SerializationTests.swift +// +// +// Created by Christian Treffs on 26.06.20. +// + +import XCTest +@testable import FirebladeECS + +public final class SerializationTests: XCTestCase { + func testSerialization() throws { + let nexus = Nexus() + nexus.createEntity(with: Position(x: 1, y: 4), Name(name: "myName")) + nexus.createEntity(with: Position(x: 5, y: 18), Name(name: "yourName")) + + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let data = try encoder.encode(nexus) + + XCTAssertNotNil(data) + XCTAssertGreaterThanOrEqual(data.count, 700) + print(String(data: data, encoding: .utf8)!) + } + + func testFailSerialization() { + let nexus = Nexus() + nexus.createEntity(with: Party(partying: true)) + + let encoder = JSONEncoder() + XCTAssertThrowsError(try encoder.encode(nexus)) + } +} + +extension Nexus: Encodable { + public func encode(to encoder: Encoder) throws { + let serialized = try serialize() + var container = encoder.singleValueContainer() + try container.encode(serialized) + } + + + public struct ComponentNotEncodableError: Swift.Error { + public let localizedDescription: String + + init(_ component: Component) { + localizedDescription = "Component `\(type(of: component))` must conform to `\(Encodable.self)` protocol to be encoded." + } + } +} + +extension Nexus { + final func serialize() throws -> SNexus { + let version = Version(major: 0, minor: 0, patch: 1) + + var componentIdMap: [ComponentIdentifier: SComponentTypeId] = [:] + var componentInstances: [SComponentId: SComponent] = [:] + var entityComponentsMap: [SEntityId: Set] = [:] + + + for entitId in self.entityStorage { + let sEntityId: SEntityId + sEntityId = SEntityId() + + + entityComponentsMap[sEntityId] = [] + let componentIds = self.get(components: entitId) ?? [] + + + for componentId in componentIds { + + let sCompTypeId: SComponentTypeId + if let typeId = componentIdMap[componentId] { + sCompTypeId = typeId + } else { + sCompTypeId = SComponentTypeId() + componentIdMap[componentId] = sCompTypeId + } + + let component = self.get(component: componentId, for: entitId)! + let sCompId: SComponentId = SComponentId() + + if let encodableComponent = component as? (Component & Encodable) { + let sComp = SComponent(typeId: sCompTypeId, instance: encodableComponent) + componentInstances[sCompId] = sComp + + entityComponentsMap[sEntityId]!.insert(sCompId) + } else { + throw ComponentNotEncodableError(component) + } + } + } + + return SNexus(version: version, + entities: entityComponentsMap, + components: componentInstances) + } +} +public typealias SEntityId = UUID +public typealias SComponentId = UUID +public typealias SComponentTypeId = UUID +public struct Version { + public let major: UInt + public let minor: UInt + public let patch: UInt +} +extension Version: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode("\(major).\(minor).\(patch)") + } +} + +public struct SNexus { + public let version: Version + public let entities: [SEntityId: Set] + public let components: [SComponentId: SComponent] + +} +extension SNexus: Encodable { } + + +public struct SComponent { + public let typeId: SComponentTypeId + public let instance: (Component & Encodable) + public init(typeId: SComponentTypeId, instance: Component & Encodable) { + self.typeId = typeId + self.instance = instance + } +} +extension SComponent: Encodable { + public enum Keys: String, CodingKey { + case typeId + case instance + } + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: Keys.self) + try container.encode(typeId, forKey: .typeId) + try instance.encode(to: container.superEncoder(forKey: .instance)) + } +} From f033421dbbb2958fc71718e90ac8a039a2b8a36c Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 10:58:01 +0200 Subject: [PATCH 02/23] Encoding works --- .../SerializationTests.swift | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index cbaa3ed2..b93b1b68 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -32,6 +32,7 @@ public final class SerializationTests: XCTestCase { } } +// MARK: - Encoding extension Nexus: Encodable { public func encode(to encoder: Encoder) throws { let serialized = try serialize() @@ -47,16 +48,14 @@ extension Nexus: Encodable { localizedDescription = "Component `\(type(of: component))` must conform to `\(Encodable.self)` protocol to be encoded." } } -} -extension Nexus { final func serialize() throws -> SNexus { let version = Version(major: 0, minor: 0, patch: 1) var componentIdMap: [ComponentIdentifier: SComponentTypeId] = [:] var componentInstances: [SComponentId: SComponent] = [:] var entityComponentsMap: [SEntityId: Set] = [:] - + var componentTypes: [SComponentType] = [] for entitId in self.entityStorage { let sEntityId: SEntityId @@ -68,6 +67,7 @@ extension Nexus { for componentId in componentIds { + let component = self.get(component: componentId, for: entitId)! let sCompTypeId: SComponentTypeId if let typeId = componentIdMap[componentId] { @@ -75,9 +75,10 @@ extension Nexus { } else { sCompTypeId = SComponentTypeId() componentIdMap[componentId] = sCompTypeId + componentTypes.append(SComponentType(typeId: sCompTypeId, typeName: String(describing: type(of: component)))) } - let component = self.get(component: componentId, for: entitId)! + let sCompId: SComponentId = SComponentId() if let encodableComponent = component as? (Component & Encodable) { @@ -93,9 +94,15 @@ extension Nexus { return SNexus(version: version, entities: entityComponentsMap, + componentTypes: componentTypes, components: componentInstances) } } + +// MARK: - Decoding + + +// MARK: - Model public typealias SEntityId = UUID public typealias SComponentId = UUID public typealias SComponentTypeId = UUID @@ -114,11 +121,17 @@ extension Version: Encodable { public struct SNexus { public let version: Version public let entities: [SEntityId: Set] + public let componentTypes: [SComponentType] public let components: [SComponentId: SComponent] } extension SNexus: Encodable { } +public struct SComponentType { + public let typeId: SComponentTypeId + public let typeName: String +} +extension SComponentType: Encodable { } public struct SComponent { public let typeId: SComponentTypeId From 3d994f0c9ae09801f0840252f487f79d65635327 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 11:37:07 +0200 Subject: [PATCH 03/23] WIP: deserialization --- Tests/FirebladeECSTests/Base.swift | 6 +- .../SerializationTests.swift | 110 ++++++++++++++---- 2 files changed, 93 insertions(+), 23 deletions(-) diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index b49a56a0..f7c45092 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -11,14 +11,14 @@ class EmptyComponent: Component { } -class Name: Component, Encodable { +class Name: Component, Codable { var name: String init(name: String) { self.name = name } } -class Position: Component, Encodable { +class Position: Component, Codable { var x: Int var y: Int init(x: Int, y: Int) { @@ -27,7 +27,7 @@ class Position: Component, Encodable { } } -class Velocity: Component, Encodable { +class Velocity: Component, Codable { var a: Float init(a: Float) { self.a = a diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index b93b1b68..fc630a96 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -30,6 +30,20 @@ public final class SerializationTests: XCTestCase { let encoder = JSONEncoder() XCTAssertThrowsError(try encoder.encode(nexus)) } + + func testDeserialization() throws { + let nexus = Nexus() + nexus.createEntity(with: Name(name: "myName")) + + let encoder = JSONEncoder() + let data = try encoder.encode(nexus) + + let decoder = JSONDecoder() + let nexus2: Nexus = try decoder.decode(Nexus.self, from: data) + + XCTAssertEqual(nexus2.numEntities, nexus.numEntities) + XCTAssertEqual(nexus2.numComponents, nexus.numComponents) + } } // MARK: - Encoding @@ -41,14 +55,14 @@ extension Nexus: Encodable { } - public struct ComponentNotEncodableError: Swift.Error { + public struct ComponentNotCodableError: Swift.Error { public let localizedDescription: String init(_ component: Component) { - localizedDescription = "Component `\(type(of: component))` must conform to `\(Encodable.self)` protocol to be encoded." + localizedDescription = "Component `\(type(of: component))` must conform to `\(Codable.self)` protocol to be encoded and decoded." } } - + final func serialize() throws -> SNexus { let version = Version(major: 0, minor: 0, patch: 1) @@ -81,14 +95,12 @@ extension Nexus: Encodable { let sCompId: SComponentId = SComponentId() - if let encodableComponent = component as? (Component & Encodable) { - let sComp = SComponent(typeId: sCompTypeId, instance: encodableComponent) - componentInstances[sCompId] = sComp - - entityComponentsMap[sEntityId]!.insert(sCompId) - } else { - throw ComponentNotEncodableError(component) - } + + let sComp = SComponent(typeId: sCompTypeId, instance: component) + componentInstances[sCompId] = sComp + + entityComponentsMap[sEntityId]!.insert(sCompId) + } } @@ -100,7 +112,24 @@ extension Nexus: Encodable { } // MARK: - Decoding - +extension Nexus: Decodable { + public convenience init(from decoder: Decoder) throws { + + let container = try decoder.singleValueContainer() + let sNexus = try container.decode(SNexus.self) + + + + + self.init(entityStorage: UnorderedSparseSet(), + componentsByType: [:], + componentsByEntity: [:], + freeEntities: [], + familyMembersByTraits: [:], + childrenByParentEntity: [:]) + } + +} // MARK: - Model public typealias SEntityId = UUID @@ -117,6 +146,32 @@ extension Version: Encodable { try container.encode("\(major).\(minor).\(patch)") } } +extension Version: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let versionString = try container.decode(String.self) + let components = versionString.components(separatedBy: ".") + guard components.count == 3 else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Malformed version.") + } + + guard let major = UInt(components[0]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Major invalid.") + } + + guard let minor = UInt(components[1]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Minor invalid.") + } + + guard let patch = UInt(components[2]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Patch invalid.") + } + + self.major = major + self.minor = minor + self.patch = patch + } +} public struct SNexus { public let version: Version @@ -125,30 +180,45 @@ public struct SNexus { public let components: [SComponentId: SComponent] } -extension SNexus: Encodable { } +extension SNexus: Encodable { } +extension SNexus: Decodable { } public struct SComponentType { public let typeId: SComponentTypeId public let typeName: String } extension SComponentType: Encodable { } +extension SComponentType: Decodable { } public struct SComponent { + public enum Keys: String, CodingKey { + case typeId + case instance + } + public let typeId: SComponentTypeId - public let instance: (Component & Encodable) - public init(typeId: SComponentTypeId, instance: Component & Encodable) { + public let instance: Component + public init(typeId: SComponentTypeId, instance: Component) { self.typeId = typeId self.instance = instance } } extension SComponent: Encodable { - public enum Keys: String, CodingKey { - case typeId - case instance - } + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: Keys.self) try container.encode(typeId, forKey: .typeId) - try instance.encode(to: container.superEncoder(forKey: .instance)) + let bytes = withUnsafeBytes(of: instance) { Data(bytes: $0.baseAddress!, count: MemoryLayout.stride(ofValue: instance)) } + try container.encode(bytes, forKey: .instance) + } +} +extension SComponent: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Keys.self) + self.typeId = try container.decode(SComponentTypeId.self, forKey: .typeId) + let instanceData = try container.decode(Data.self, forKey: .instance) + self.instance = instanceData.withUnsafeBytes { + $0.baseAddress!.load(as: Component.self) + } } } From 8c6211e9787872acc6cb661de0e8669ae4604762 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 13:25:08 +0200 Subject: [PATCH 04/23] Decoding working - unstable identifiers --- Sources/FirebladeECS/EntityIdentifier.swift | 1 - Sources/FirebladeECS/Nexus+Entity.swift | 10 ++- Tests/FirebladeECSTests/Base.swift | 6 +- .../SerializationTests.swift | 82 +++++++++++-------- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/Sources/FirebladeECS/EntityIdentifier.swift b/Sources/FirebladeECS/EntityIdentifier.swift index 0ccbadee..33075b45 100644 --- a/Sources/FirebladeECS/EntityIdentifier.swift +++ b/Sources/FirebladeECS/EntityIdentifier.swift @@ -19,4 +19,3 @@ public struct EntityIdentifier { extension EntityIdentifier: Equatable { } extension EntityIdentifier: Hashable { } -extension EntityIdentifier: Codable { } diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index 663d9279..d3010f83 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -17,9 +17,13 @@ extension Nexus { @discardableResult public func createEntity() -> Entity { let newEntityIdentifier: EntityIdentifier = nextEntityId() - entityStorage.insert(newEntityIdentifier, at: newEntityIdentifier.id) - delegate?.nexusEvent(EntityCreated(entityId: newEntityIdentifier)) - return Entity(nexus: self, id: newEntityIdentifier) + return createEntity(entityId: newEntityIdentifier) + } + + internal func createEntity(entityId: EntityIdentifier) -> Entity { + entityStorage.insert(entityId, at: entityId.id) + delegate?.nexusEvent(EntityCreated(entityId: entityId)) + return Entity(nexus: self, id: entityId) } @discardableResult diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index f7c45092..ded9b861 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -11,14 +11,14 @@ class EmptyComponent: Component { } -class Name: Component, Codable { +class Name: Component { var name: String init(name: String) { self.name = name } } -class Position: Component, Codable { +class Position: Component { var x: Int var y: Int init(x: Int, y: Int) { @@ -27,7 +27,7 @@ class Position: Component, Codable { } } -class Velocity: Component, Codable { +class Velocity: Component { var a: Float init(a: Float) { self.a = a diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index fc630a96..9e46d311 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -23,17 +23,10 @@ public final class SerializationTests: XCTestCase { print(String(data: data, encoding: .utf8)!) } - func testFailSerialization() { - let nexus = Nexus() - nexus.createEntity(with: Party(partying: true)) - - let encoder = JSONEncoder() - XCTAssertThrowsError(try encoder.encode(nexus)) - } - func testDeserialization() throws { let nexus = Nexus() - nexus.createEntity(with: Name(name: "myName")) + let firstEntity = nexus.createEntity(with: Name(name: "myName"), Position(x: 1, y: 2)) + let secondEntity = nexus.createEntity(with: Velocity(a: 3.14), Party(partying: true)) let encoder = JSONEncoder() let data = try encoder.encode(nexus) @@ -41,6 +34,21 @@ public final class SerializationTests: XCTestCase { let decoder = JSONDecoder() let nexus2: Nexus = try decoder.decode(Nexus.self, from: data) + + let firstEntity2 = nexus2.get(entity: firstEntity.identifier)! + XCTAssertTrue(firstEntity2.has(Name.self)) + XCTAssertTrue(firstEntity2.has(Position.self)) + XCTAssertEqual(firstEntity2.get(component: Name.self)?.name, "myName") + XCTAssertEqual(firstEntity2.get(component: Position.self)?.x, 1) + XCTAssertEqual(firstEntity2.get(component: Position.self)?.y, 2) + + + let secondEntity2 = nexus2.get(entity: secondEntity.identifier)! + XCTAssertTrue(secondEntity2.has(Velocity.self)) + XCTAssertTrue(secondEntity2.has(Party.self)) + XCTAssertEqual(secondEntity2.get(component: Velocity.self)?.a, 3.14) + XCTAssertEqual(secondEntity2.get(component: Party.self)?.partying, true) + XCTAssertEqual(nexus2.numEntities, nexus.numEntities) XCTAssertEqual(nexus2.numComponents, nexus.numComponents) } @@ -54,29 +62,17 @@ extension Nexus: Encodable { try container.encode(serialized) } - - public struct ComponentNotCodableError: Swift.Error { - public let localizedDescription: String - - init(_ component: Component) { - localizedDescription = "Component `\(type(of: component))` must conform to `\(Codable.self)` protocol to be encoded and decoded." - } - } - final func serialize() throws -> SNexus { let version = Version(major: 0, minor: 0, patch: 1) var componentIdMap: [ComponentIdentifier: SComponentTypeId] = [:] var componentInstances: [SComponentId: SComponent] = [:] - var entityComponentsMap: [SEntityId: Set] = [:] + var entityComponentsMap: [EntityIdentifier: Set] = [:] var componentTypes: [SComponentType] = [] for entitId in self.entityStorage { - let sEntityId: SEntityId - sEntityId = SEntityId() - - entityComponentsMap[sEntityId] = [] + entityComponentsMap[entitId] = [] let componentIds = self.get(components: entitId) ?? [] @@ -99,7 +95,7 @@ extension Nexus: Encodable { let sComp = SComponent(typeId: sCompTypeId, instance: component) componentInstances[sCompId] = sComp - entityComponentsMap[sEntityId]!.insert(sCompId) + entityComponentsMap[entitId]!.insert(sCompId) } } @@ -118,23 +114,43 @@ extension Nexus: Decodable { let container = try decoder.singleValueContainer() let sNexus = try container.decode(SNexus.self) + self.init() + for (entityId, componentSet) in sNexus.entities { + let entity = self.createEntity(entityId: entityId) + + for sCompId in componentSet { + guard let sComp = sNexus.components[sCompId] else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not find component instance for \(sCompId).")) + } + entity.assign(sComp.instance) + } + + } - - self.init(entityStorage: UnorderedSparseSet(), - componentsByType: [:], - componentsByEntity: [:], - freeEntities: [], - familyMembersByTraits: [:], - childrenByParentEntity: [:]) } } // MARK: - Model -public typealias SEntityId = UUID public typealias SComponentId = UUID public typealias SComponentTypeId = UUID + +extension EntityIdentifier: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + 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) + } +} + + public struct Version { public let major: UInt public let minor: UInt @@ -175,7 +191,7 @@ extension Version: Decodable { public struct SNexus { public let version: Version - public let entities: [SEntityId: Set] + public let entities: [EntityIdentifier: Set] public let componentTypes: [SComponentType] public let components: [SComponentId: SComponent] From b37c4ce8e243a0c1b8916ea4dc30af08f6f34fb4 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 13:26:19 +0200 Subject: [PATCH 05/23] Remove unused components types --- Tests/FirebladeECSTests/SerializationTests.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index 9e46d311..22172466 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -68,7 +68,6 @@ extension Nexus: Encodable { var componentIdMap: [ComponentIdentifier: SComponentTypeId] = [:] var componentInstances: [SComponentId: SComponent] = [:] var entityComponentsMap: [EntityIdentifier: Set] = [:] - var componentTypes: [SComponentType] = [] for entitId in self.entityStorage { @@ -85,7 +84,6 @@ extension Nexus: Encodable { } else { sCompTypeId = SComponentTypeId() componentIdMap[componentId] = sCompTypeId - componentTypes.append(SComponentType(typeId: sCompTypeId, typeName: String(describing: type(of: component)))) } @@ -102,7 +100,6 @@ extension Nexus: Encodable { return SNexus(version: version, entities: entityComponentsMap, - componentTypes: componentTypes, components: componentInstances) } } @@ -192,20 +189,12 @@ extension Version: Decodable { public struct SNexus { public let version: Version public let entities: [EntityIdentifier: Set] - public let componentTypes: [SComponentType] public let components: [SComponentId: SComponent] } extension SNexus: Encodable { } extension SNexus: Decodable { } -public struct SComponentType { - public let typeId: SComponentTypeId - public let typeName: String -} -extension SComponentType: Encodable { } -extension SComponentType: Decodable { } - public struct SComponent { public enum Keys: String, CodingKey { case typeId From 71767805b9be8433e36af3f666c80f96c341f9f5 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 14:33:32 +0200 Subject: [PATCH 06/23] Stabilize encoding --- .../FirebladeECS/ComponentIdentifier.swift | 17 +++++- Sources/FirebladeECS/Nexus.swift | 21 ------- .../SerializationTests.swift | 55 ++++++++----------- 3 files changed, 38 insertions(+), 55 deletions(-) diff --git a/Sources/FirebladeECS/ComponentIdentifier.swift b/Sources/FirebladeECS/ComponentIdentifier.swift index b3ef5624..3ff5cce6 100644 --- a/Sources/FirebladeECS/ComponentIdentifier.swift +++ b/Sources/FirebladeECS/ComponentIdentifier.swift @@ -18,7 +18,22 @@ public struct ComponentIdentifier { extension ComponentIdentifier { @usableFromInline init(_ componentType: C.Type) where C: Component { - self.hash = Nexus.makeOrGetComponentId(componentType) + self.hash = Self.makeRuntimeHash(componentType) + } + + /// object identifier hash (only stable during runtime) - arbitrary hash is ok. + internal static func makeRuntimeHash(_ componentType: C.Type) -> Hash where C: Component { + ObjectIdentifier(componentType).hashValue + } + + internal static func makeStableTypeHash(component: Component) -> StableId { + let componentTypeString = String(describing: type(of: component)) + return StringHashing.singer_djb2(componentTypeString) + } + + internal static func makeStableInstanceHash(component: Component, entityId: EntityIdentifier) -> StableId { + let componentTypeString = String(describing: type(of: component)) + String(entityId.id) + return StringHashing.singer_djb2(componentTypeString) } } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index cbc630f4..f4843a50 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -69,27 +69,6 @@ public final class Nexus { familyMembersByTraits.removeAll() childrenByParentEntity.removeAll() } - - public static var knownUniqueComponentTypes: Set { - Set(stableComponentIdentifierMap.keys.map { ComponentIdentifier(hash: $0) }) - } -} - -// MARK: - centralized component identifier mapping -extension Nexus { - internal static var stableComponentIdentifierMap: [ComponentIdentifier.Hash: ComponentIdentifier.StableId] = [:] - - internal static func makeOrGetComponentId(_ 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 diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index 22172466..f0ed01c4 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -15,12 +15,17 @@ public final class SerializationTests: XCTestCase { nexus.createEntity(with: Position(x: 5, y: 18), Name(name: "yourName")) let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted let data = try encoder.encode(nexus) XCTAssertNotNil(data) - XCTAssertGreaterThanOrEqual(data.count, 700) - print(String(data: data, encoding: .utf8)!) + XCTAssertGreaterThanOrEqual(data.count, 400) + + + let encoder2 = JSONEncoder() + let data2 = try encoder2.encode(nexus) + XCTAssertEqual(data.count, data2.count) + //print(String(data: data, encoding: .utf8)!) + //print(String(data: data2, encoding: .utf8)!) } func testDeserialization() throws { @@ -65,36 +70,22 @@ extension Nexus: Encodable { final func serialize() throws -> SNexus { let version = Version(major: 0, minor: 0, patch: 1) - var componentIdMap: [ComponentIdentifier: SComponentTypeId] = [:] - var componentInstances: [SComponentId: SComponent] = [:] - var entityComponentsMap: [EntityIdentifier: Set] = [:] + var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] + var entityComponentsMap: [EntityIdentifier: Set] = [:] for entitId in self.entityStorage { entityComponentsMap[entitId] = [] let componentIds = self.get(components: entitId) ?? [] - for componentId in componentIds { - let component = self.get(component: componentId, for: entitId)! - - let sCompTypeId: SComponentTypeId - if let typeId = componentIdMap[componentId] { - sCompTypeId = typeId - } else { - sCompTypeId = SComponentTypeId() - componentIdMap[componentId] = sCompTypeId + guard let component = self.get(component: componentId, for: entitId) else { + fatalError("could not get entity for \(componentId)") } - - - let sCompId: SComponentId = SComponentId() - - - let sComp = SComponent(typeId: sCompTypeId, instance: component) - componentInstances[sCompId] = sComp - - entityComponentsMap[entitId]!.insert(sCompId) - + let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) + let componentStableTypeHash = ComponentIdentifier.makeStableTypeHash(component: component) + componentInstances[componentStableInstanceHash] = SComponent(typeId: componentStableTypeHash, instance: component) + entityComponentsMap[entitId]!.insert(componentStableInstanceHash) } } @@ -130,9 +121,6 @@ extension Nexus: Decodable { } // MARK: - Model -public typealias SComponentId = UUID -public typealias SComponentTypeId = UUID - extension EntityIdentifier: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -153,6 +141,7 @@ public struct Version { public let minor: UInt public let patch: UInt } +extension Version: Equatable { } extension Version: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -188,8 +177,8 @@ extension Version: Decodable { public struct SNexus { public let version: Version - public let entities: [EntityIdentifier: Set] - public let components: [SComponentId: SComponent] + public let entities: [EntityIdentifier: Set] + public let components: [ComponentIdentifier.StableId: SComponent] } extension SNexus: Encodable { } @@ -201,9 +190,9 @@ public struct SComponent { case instance } - public let typeId: SComponentTypeId + public let typeId: ComponentIdentifier.StableId public let instance: Component - public init(typeId: SComponentTypeId, instance: Component) { + public init(typeId: ComponentIdentifier.StableId, instance: Component) { self.typeId = typeId self.instance = instance } @@ -220,7 +209,7 @@ extension SComponent: Encodable { extension SComponent: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) - self.typeId = try container.decode(SComponentTypeId.self, forKey: .typeId) + self.typeId = try container.decode(ComponentIdentifier.StableId.self, forKey: .typeId) let instanceData = try container.decode(Data.self, forKey: .instance) self.instance = instanceData.withUnsafeBytes { $0.baseAddress!.load(as: Component.self) From d76e988f02519b7c05731e48bacb764bc50de90e Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 15:39:46 +0200 Subject: [PATCH 07/23] Rework entityId generation --- .../EntityIdentifierGenerator.swift | 35 +++++++++++++++++++ Sources/FirebladeECS/Nexus+Entity.swift | 16 ++------- Sources/FirebladeECS/Nexus.swift | 9 +++-- Tests/FirebladeECSTests/EntityTests.swift | 25 +++++++++++++ .../SerializationTests.swift | 17 ++++++--- Tests/FirebladeECSTests/SystemsTests.swift | 16 ++++----- 6 files changed, 86 insertions(+), 32 deletions(-) create mode 100644 Sources/FirebladeECS/EntityIdentifierGenerator.swift diff --git a/Sources/FirebladeECS/EntityIdentifierGenerator.swift b/Sources/FirebladeECS/EntityIdentifierGenerator.swift new file mode 100644 index 00000000..0e805a26 --- /dev/null +++ b/Sources/FirebladeECS/EntityIdentifierGenerator.swift @@ -0,0 +1,35 @@ +// +// EntityIdentifierGenerator.swift +// +// +// Created by Christian Treffs on 26.06.20. +// + +internal final class EntityIdentifierGenerator { + private var stack: [UInt32] + + var count: Int { + stack.count + } + + init() { + stack = [0] + } + + init(_ entityIds: [EntityIdentifier]) { + stack = entityIds.reversed().map { UInt32($0.id) } + } + + func nextId() -> EntityIdentifier { + if stack.count == 1 { + defer { stack[0] += 1 } + return EntityIdentifier(stack[0]) + } else { + return EntityIdentifier(stack.removeLast()) + } + } + + func freeId(_ entityId: EntityIdentifier) { + stack.append(UInt32(entityId.id)) + } +} diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index d3010f83..bc5c637c 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -6,21 +6,9 @@ // extension Nexus { - @inlinable - internal func nextEntityId() -> EntityIdentifier { - guard let nextReused: EntityIdentifier = freeEntities.popLast() else { - return EntityIdentifier(UInt32(entityStorage.count)) - } - return nextReused - } - @discardableResult public func createEntity() -> Entity { - let newEntityIdentifier: EntityIdentifier = nextEntityId() - return createEntity(entityId: newEntityIdentifier) - } - - internal func createEntity(entityId: EntityIdentifier) -> Entity { + let entityId: EntityIdentifier = entityIdGenerator.nextId() entityStorage.insert(entityId, at: entityId.id) delegate?.nexusEvent(EntityCreated(entityId: entityId)) return Entity(nexus: self, id: entityId) @@ -71,7 +59,7 @@ extension Nexus { update(familyMembership: entityId) } - freeEntities.append(entityId) + entityIdGenerator.freeId(entityId) delegate?.nexusEvent(EntityDestroyed(entityId: entityId)) return true diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index f4843a50..f2c5b05f 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -11,7 +11,7 @@ public final class Nexus { @usableFromInline final var entityStorage: UnorderedSparseSet /// Entity ids that are currently not used. - @usableFromInline final var freeEntities: [EntityIdentifier] + let entityIdGenerator: EntityIdentifierGenerator /// - Key: ComponentIdentifier aka component type. /// - Value: Array of component instances of same type (uniform). @@ -37,7 +37,7 @@ public final class Nexus { self.init(entityStorage: UnorderedSparseSet(), componentsByType: [:], componentsByEntity: [:], - freeEntities: [], + entityIdGenerator: EntityIdentifierGenerator(), familyMembersByTraits: [:], childrenByParentEntity: [:]) } @@ -45,15 +45,15 @@ public final class Nexus { internal init(entityStorage: UnorderedSparseSet, componentsByType: [ComponentIdentifier: ManagedContiguousArray], componentsByEntity: [EntityIdentifier: Set], - freeEntities: [EntityIdentifier], + entityIdGenerator: EntityIdentifierGenerator, familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet], childrenByParentEntity: [EntityIdentifier: Set]) { self.entityStorage = entityStorage self.componentsByType = componentsByType self.componentIdsByEntity = componentsByEntity - self.freeEntities = freeEntities self.familyMembersByTraits = familyMembersByTraits self.childrenByParentEntity = childrenByParentEntity + self.entityIdGenerator = entityIdGenerator } deinit { @@ -63,7 +63,6 @@ public final class Nexus { public final func clear() { entityStorage.forEach { destroy(entityId: $0) } entityStorage.removeAll() - freeEntities.removeAll() componentsByType.removeAll() componentIdsByEntity.removeAll() familyMembersByTraits.removeAll() diff --git a/Tests/FirebladeECSTests/EntityTests.swift b/Tests/FirebladeECSTests/EntityTests.swift index d4cb2c64..c4a059b6 100644 --- a/Tests/FirebladeECSTests/EntityTests.swift +++ b/Tests/FirebladeECSTests/EntityTests.swift @@ -39,6 +39,31 @@ class EntityTests: XCTestCase { XCTAssertTrue(allComponents.elementsEqualUnordered(expectedComponents) { $0 === $1 }) } + + func testEntityIdGenerator() { + let generator = EntityIdentifierGenerator() + + XCTAssertEqual(generator.count, 1) + + for _ in 0..<100 { + _ = generator.nextId() + } + + XCTAssertEqual(generator.count, 1) + + + for i in 10..<60 { + generator.freeId(EntityIdentifier(UInt32(i))) + } + + XCTAssertEqual(generator.count, 51) + + for _ in 0..<50 { + _ = generator.nextId() + } + + XCTAssertEqual(generator.count, 1) + } } extension Sequence { diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index f0ed01c4..a5f718d1 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -102,11 +102,18 @@ extension Nexus: Decodable { let container = try decoder.singleValueContainer() let sNexus = try container.decode(SNexus.self) - self.init() - - for (entityId, componentSet) in sNexus.entities { - let entity = self.createEntity(entityId: entityId) - + let entityIds = sNexus.entities.map { $0.key } + + self.init(entityStorage: UnorderedSparseSet(), + componentsByType: [:], + componentsByEntity: [:], + entityIdGenerator: EntityIdentifierGenerator(entityIds), + familyMembersByTraits: [:], + childrenByParentEntity: [:]) + + for componentSet in sNexus.entities.values { + let entity = self.createEntity() + print(entity.identifier) for sCompId in componentSet { guard let sComp = sNexus.components[sCompId] else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not find component instance for \(sCompId).")) diff --git a/Tests/FirebladeECSTests/SystemsTests.swift b/Tests/FirebladeECSTests/SystemsTests.swift index ac815bdd..59005b2b 100644 --- a/Tests/FirebladeECSTests/SystemsTests.swift +++ b/Tests/FirebladeECSTests/SystemsTests.swift @@ -38,7 +38,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, 0) XCTAssertEqual(colorSystem.colors.memberIds.count, 0) XCTAssertEqual(positionSystem.positions.memberIds.count, 0) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, 0) batchCreateEntities(count: num) @@ -47,7 +47,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) colorSystem.update() positionSystem.update() @@ -56,7 +56,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) batchCreateEntities(count: num) @@ -64,7 +64,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) colorSystem.update() positionSystem.update() @@ -73,12 +73,12 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) batchDestroyEntities(count: num) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) - XCTAssertEqual(nexus.freeEntities.count, num) + XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num) XCTAssertEqual(nexus.numEntities, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) @@ -90,7 +90,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.freeEntities.count, num) + XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num) batchCreateEntities(count: num) @@ -98,7 +98,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) } func createDefaultEntity() { From 4f9e6bffca5738c38367bbb7588fe412204ef84e Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 15:50:28 +0200 Subject: [PATCH 08/23] Add serialization to library --- .../FirebladeECS/Nexus+Serialization.swift | 175 ++++++++++++++++++ .../SerializationTests.swift | 167 +---------------- 2 files changed, 176 insertions(+), 166 deletions(-) create mode 100644 Sources/FirebladeECS/Nexus+Serialization.swift diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift new file mode 100644 index 00000000..c980ba19 --- /dev/null +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -0,0 +1,175 @@ +// +// Nexus+Serialization.swift +// +// +// Created by Christian Treffs on 26.06.20. +// + +#if canImport(Foundation) +import struct Foundation.Data + +// MARK: - Encoding +extension Nexus: Encodable { + public func encode(to encoder: Encoder) throws { + let serialized = try serialize() + var container = encoder.singleValueContainer() + try container.encode(serialized) + } + + final func serialize() throws -> SNexus { + let version = Version.base + + var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] + var entityComponentsMap: [EntityIdentifier: Set] = [:] + + for entitId in self.entityStorage { + entityComponentsMap[entitId] = [] + let componentIds = self.get(components: entitId) ?? [] + + for componentId in componentIds { + guard let component = self.get(component: componentId, for: entitId) else { + fatalError("could not get entity for \(componentId)") + } + let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) + let componentStableTypeHash = ComponentIdentifier.makeStableTypeHash(component: component) + componentInstances[componentStableInstanceHash] = SComponent(typeId: componentStableTypeHash, instance: component) + entityComponentsMap[entitId]?.insert(componentStableInstanceHash) + } + } + + return SNexus(version: version, + entities: entityComponentsMap, + components: componentInstances) + } +} + +// MARK: - Decoding +extension Nexus: Decodable { + public convenience init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let sNexus = try container.decode(SNexus.self) + + let entityIds = sNexus.entities.map { $0.key } + + self.init(entityStorage: UnorderedSparseSet(), + componentsByType: [:], + componentsByEntity: [:], + entityIdGenerator: EntityIdentifierGenerator(entityIds), + familyMembersByTraits: [:], + childrenByParentEntity: [:]) + + for componentSet in sNexus.entities.values { + let entity = self.createEntity() + print(entity.identifier) + for sCompId in componentSet { + guard let sComp = sNexus.components[sCompId] else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not find component instance for \(sCompId).")) + } + entity.assign(sComp.instance) + } + } + } +} + +extension EntityIdentifier: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + 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) + } +} + +// MARK: - Serialization Model +public struct Version { + public let major: UInt + public let minor: UInt + public let patch: UInt +} +extension Version { + // Base version. Supports entity and component de-/encoding. + static let base = Version(major: 1, minor: 0, patch: 0) +} +extension Version: Equatable { } +extension Version: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode("\(major).\(minor).\(patch)") + } +} +extension Version: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let versionString = try container.decode(String.self) + let components = versionString.components(separatedBy: ".") + guard components.count == 3 else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Malformed version.") + } + + guard let major = UInt(components[0]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Major invalid.") + } + + guard let minor = UInt(components[1]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Minor invalid.") + } + + guard let patch = UInt(components[2]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Patch invalid.") + } + + self.major = major + self.minor = minor + self.patch = patch + } +} + +internal struct SNexus { + let version: Version + let entities: [EntityIdentifier: Set] + let components: [ComponentIdentifier.StableId: SComponent] +} +extension SNexus: Encodable { } +extension SNexus: Decodable { } + +internal struct SComponent { + enum Keys: String, CodingKey { + case typeId + case instance + } + + let typeId: ComponentIdentifier.StableId + let instance: Component + + init(typeId: ComponentIdentifier.StableId, instance: Component) { + self.typeId = typeId + self.instance = instance + } +} +extension SComponent: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: Keys.self) + try container.encode(typeId, forKey: .typeId) + let bytes = withUnsafeBytes(of: instance) { + Data(bytes: $0.baseAddress!, count: MemoryLayout.stride(ofValue: instance)) + } + try container.encode(bytes, forKey: .instance) + } +} +extension SComponent: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Keys.self) + self.typeId = try container.decode(ComponentIdentifier.StableId.self, forKey: .typeId) + let instanceData = try container.decode(Data.self, forKey: .instance) + self.instance = instanceData.withUnsafeBytes { + $0.baseAddress!.load(as: Component.self) + } + } +} + +#endif diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index a5f718d1..0b6cc3ec 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -6,7 +6,7 @@ // import XCTest -@testable import FirebladeECS +import FirebladeECS public final class SerializationTests: XCTestCase { func testSerialization() throws { @@ -58,168 +58,3 @@ public final class SerializationTests: XCTestCase { XCTAssertEqual(nexus2.numComponents, nexus.numComponents) } } - -// MARK: - Encoding -extension Nexus: Encodable { - public func encode(to encoder: Encoder) throws { - let serialized = try serialize() - var container = encoder.singleValueContainer() - try container.encode(serialized) - } - - final func serialize() throws -> SNexus { - let version = Version(major: 0, minor: 0, patch: 1) - - var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] - var entityComponentsMap: [EntityIdentifier: Set] = [:] - - for entitId in self.entityStorage { - - entityComponentsMap[entitId] = [] - let componentIds = self.get(components: entitId) ?? [] - - for componentId in componentIds { - guard let component = self.get(component: componentId, for: entitId) else { - fatalError("could not get entity for \(componentId)") - } - let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) - let componentStableTypeHash = ComponentIdentifier.makeStableTypeHash(component: component) - componentInstances[componentStableInstanceHash] = SComponent(typeId: componentStableTypeHash, instance: component) - entityComponentsMap[entitId]!.insert(componentStableInstanceHash) - } - } - - return SNexus(version: version, - entities: entityComponentsMap, - components: componentInstances) - } -} - -// MARK: - Decoding -extension Nexus: Decodable { - public convenience init(from decoder: Decoder) throws { - - let container = try decoder.singleValueContainer() - let sNexus = try container.decode(SNexus.self) - - let entityIds = sNexus.entities.map { $0.key } - - self.init(entityStorage: UnorderedSparseSet(), - componentsByType: [:], - componentsByEntity: [:], - entityIdGenerator: EntityIdentifierGenerator(entityIds), - familyMembersByTraits: [:], - childrenByParentEntity: [:]) - - for componentSet in sNexus.entities.values { - let entity = self.createEntity() - print(entity.identifier) - for sCompId in componentSet { - guard let sComp = sNexus.components[sCompId] else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not find component instance for \(sCompId).")) - } - entity.assign(sComp.instance) - } - - } - - } - -} - -// MARK: - Model -extension EntityIdentifier: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - 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) - } -} - - -public struct Version { - public let major: UInt - public let minor: UInt - public let patch: UInt -} -extension Version: Equatable { } -extension Version: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode("\(major).\(minor).\(patch)") - } -} -extension Version: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let versionString = try container.decode(String.self) - let components = versionString.components(separatedBy: ".") - guard components.count == 3 else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Malformed version.") - } - - guard let major = UInt(components[0]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Major invalid.") - } - - guard let minor = UInt(components[1]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Minor invalid.") - } - - guard let patch = UInt(components[2]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Patch invalid.") - } - - self.major = major - self.minor = minor - self.patch = patch - } -} - -public struct SNexus { - public let version: Version - public let entities: [EntityIdentifier: Set] - public let components: [ComponentIdentifier.StableId: SComponent] - -} -extension SNexus: Encodable { } -extension SNexus: Decodable { } - -public struct SComponent { - public enum Keys: String, CodingKey { - case typeId - case instance - } - - public let typeId: ComponentIdentifier.StableId - public let instance: Component - public init(typeId: ComponentIdentifier.StableId, instance: Component) { - self.typeId = typeId - self.instance = instance - } -} -extension SComponent: Encodable { - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: Keys.self) - try container.encode(typeId, forKey: .typeId) - let bytes = withUnsafeBytes(of: instance) { Data(bytes: $0.baseAddress!, count: MemoryLayout.stride(ofValue: instance)) } - try container.encode(bytes, forKey: .instance) - } -} -extension SComponent: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Keys.self) - self.typeId = try container.decode(ComponentIdentifier.StableId.self, forKey: .typeId) - let instanceData = try container.decode(Data.self, forKey: .instance) - self.instance = instanceData.withUnsafeBytes { - $0.baseAddress!.load(as: Component.self) - } - } -} From 49d0c8f516f400d70d4f67eadc6fec55180e34f9 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 26 Jun 2020 16:02:14 +0200 Subject: [PATCH 09/23] Refine format --- .../FirebladeECS/Nexus+Serialization.swift | 22 +++++-------------- .../SerializationTests.swift | 2 +- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index c980ba19..fa26f7aa 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -31,8 +31,7 @@ extension Nexus: Encodable { fatalError("could not get entity for \(componentId)") } let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) - let componentStableTypeHash = ComponentIdentifier.makeStableTypeHash(component: component) - componentInstances[componentStableInstanceHash] = SComponent(typeId: componentStableTypeHash, instance: component) + componentInstances[componentStableInstanceHash] = SComponent(instance: component) entityComponentsMap[entitId]?.insert(componentStableInstanceHash) } } @@ -138,34 +137,25 @@ extension SNexus: Encodable { } extension SNexus: Decodable { } internal struct SComponent { - enum Keys: String, CodingKey { - case typeId - case instance - } - - let typeId: ComponentIdentifier.StableId let instance: Component - init(typeId: ComponentIdentifier.StableId, instance: Component) { - self.typeId = typeId + init(instance: Component) { self.instance = instance } } extension SComponent: Encodable { public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: Keys.self) - try container.encode(typeId, forKey: .typeId) + var container = encoder.singleValueContainer() let bytes = withUnsafeBytes(of: instance) { Data(bytes: $0.baseAddress!, count: MemoryLayout.stride(ofValue: instance)) } - try container.encode(bytes, forKey: .instance) + try container.encode(bytes) } } extension SComponent: Decodable { public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Keys.self) - self.typeId = try container.decode(ComponentIdentifier.StableId.self, forKey: .typeId) - let instanceData = try container.decode(Data.self, forKey: .instance) + let container = try decoder.singleValueContainer() + let instanceData = try container.decode(Data.self) self.instance = instanceData.withUnsafeBytes { $0.baseAddress!.load(as: Component.self) } diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index 0b6cc3ec..b4638db7 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -18,7 +18,7 @@ public final class SerializationTests: XCTestCase { let data = try encoder.encode(nexus) XCTAssertNotNil(data) - XCTAssertGreaterThanOrEqual(data.count, 400) + XCTAssertEqual(data.count, 307) let encoder2 = JSONEncoder() From 2fbbf4278c66a819cd1697d97321f9bf0cefc75d Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 9 Jul 2020 23:25:04 +0200 Subject: [PATCH 10/23] Cleanup --- Sources/FirebladeECS/Nexus+Serialization.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index fa26f7aa..5918267c 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -59,7 +59,6 @@ extension Nexus: Decodable { for componentSet in sNexus.entities.values { let entity = self.createEntity() - print(entity.identifier) for sCompId in componentSet { guard let sComp = sNexus.components[sCompId] else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not find component instance for \(sCompId).")) From 9fc12c4657dc7e3311f856685f2f2a655205619d Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Tue, 14 Jul 2020 16:10:52 +0200 Subject: [PATCH 11/23] Cleanup serialization --- Sources/FirebladeECS/Nexus+Codable.swift | 22 +++ .../FirebladeECS/Nexus+Serialization.swift | 126 +++++++----------- Sources/FirebladeECS/Version.swift | 46 +++++++ .../SerializationTests.swift | 2 +- 4 files changed, 116 insertions(+), 80 deletions(-) create mode 100644 Sources/FirebladeECS/Nexus+Codable.swift create mode 100644 Sources/FirebladeECS/Version.swift diff --git a/Sources/FirebladeECS/Nexus+Codable.swift b/Sources/FirebladeECS/Nexus+Codable.swift new file mode 100644 index 00000000..377f54c0 --- /dev/null +++ b/Sources/FirebladeECS/Nexus+Codable.swift @@ -0,0 +1,22 @@ +// +// Nexus+Codable.swift +// +// +// Created by Christian Treffs on 14.07.20. +// + +extension Nexus: Encodable { + public func encode(to encoder: Encoder) throws { + let serializedNexus = try self.serialize() + var container = encoder.singleValueContainer() + try container.encode(serializedNexus) + } +} + +extension Nexus: Decodable { + public convenience init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let sNexus = try container.decode(SNexus.self) + try self.init(from: sNexus) + } +} diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index 5918267c..f5a3948d 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -8,18 +8,11 @@ #if canImport(Foundation) import struct Foundation.Data -// MARK: - Encoding -extension Nexus: Encodable { - public func encode(to encoder: Encoder) throws { - let serialized = try serialize() - var container = encoder.singleValueContainer() - try container.encode(serialized) - } - +extension Nexus { final func serialize() throws -> SNexus { - let version = Version.base + let version = Version(major: 1, minor: 0, patch: 0) - var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] + var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] var entityComponentsMap: [EntityIdentifier: Set] = [:] for entitId in self.entityStorage { @@ -27,11 +20,9 @@ extension Nexus: Encodable { let componentIds = self.get(components: entitId) ?? [] for componentId in componentIds { - guard let component = self.get(component: componentId, for: entitId) else { - fatalError("could not get entity for \(componentId)") - } + let component = self.get(unsafeComponent: componentId, for: entitId) let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) - componentInstances[componentStableInstanceHash] = SComponent(instance: component) + componentInstances[componentStableInstanceHash] = SComponent.component(component) entityComponentsMap[entitId]?.insert(componentStableInstanceHash) } } @@ -40,14 +31,8 @@ extension Nexus: Encodable { entities: entityComponentsMap, components: componentInstances) } -} - -// MARK: - Decoding -extension Nexus: Decodable { - public convenience init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let sNexus = try container.decode(SNexus.self) + convenience init(from sNexus: SNexus) throws { let entityIds = sNexus.entities.map { $0.key } self.init(entityStorage: UnorderedSparseSet(), @@ -61,9 +46,14 @@ extension Nexus: Decodable { let entity = self.createEntity() for sCompId in componentSet { guard let sComp = sNexus.components[sCompId] else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not find component instance for \(sCompId).")) + // FIXME: we want a dedicated error + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Could not find component instance for \(sCompId).")) + } + + switch sComp { + case let .component(comp): + entity.assign(comp) } - entity.assign(sComp.instance) } } } @@ -83,81 +73,59 @@ extension EntityIdentifier: Decodable { } } -// MARK: - Serialization Model -public struct Version { - public let major: UInt - public let minor: UInt - public let patch: UInt -} -extension Version { - // Base version. Supports entity and component de-/encoding. - static let base = Version(major: 1, minor: 0, patch: 0) +internal struct SNexus { + let version: Version + let entities: [EntityIdentifier: Set] + let components: [ComponentIdentifier.StableId: SComponent] } -extension Version: Equatable { } -extension Version: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode("\(major).\(minor).\(patch)") - } +extension SNexus: Encodable { } +extension SNexus: Decodable { } + +protocol ComponentEncoding { + static func encode(component: Component, to encoder: Encoder) throws } -extension Version: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let versionString = try container.decode(String.self) - let components = versionString.components(separatedBy: ".") - guard components.count == 3 else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Malformed version.") - } - guard let major = UInt(components[0]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Major invalid.") - } +protocol ComponentDecoding { + static func decode(from decoder: Decoder) throws -> Component +} +typealias ComponentCodable = ComponentEncoding & ComponentDecoding - guard let minor = UInt(components[1]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Minor invalid.") +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) + } +} - guard let patch = UInt(components[2]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Patch invalid.") +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) } - - self.major = major - self.minor = minor - self.patch = patch } } -internal struct SNexus { - let version: Version - let entities: [EntityIdentifier: Set] - let components: [ComponentIdentifier.StableId: SComponent] +enum SComponent { + case component(Component) } -extension SNexus: Encodable { } -extension SNexus: Decodable { } - -internal struct SComponent { - let instance: Component - init(instance: Component) { - self.instance = instance - } -} extension SComponent: Encodable { public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - let bytes = withUnsafeBytes(of: instance) { - Data(bytes: $0.baseAddress!, count: MemoryLayout.stride(ofValue: instance)) + switch self { + case let .component(comp): + try CodingStrategy.encode(component: comp, to: encoder) } - try container.encode(bytes) } } + extension SComponent: Decodable { public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let instanceData = try container.decode(Data.self) - self.instance = instanceData.withUnsafeBytes { - $0.baseAddress!.load(as: Component.self) - } + self = .component(try CodingStrategy.decode(from: decoder)) } } diff --git a/Sources/FirebladeECS/Version.swift b/Sources/FirebladeECS/Version.swift new file mode 100644 index 00000000..bcb3f61a --- /dev/null +++ b/Sources/FirebladeECS/Version.swift @@ -0,0 +1,46 @@ +// +// Version.swift +// +// +// Created by Christian Treffs on 14.07.20. +// + +public struct Version { + public let major: UInt + public let minor: UInt + public let patch: UInt +} + +extension Version: Equatable { } +extension Version: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode("\(major).\(minor).\(patch)") + } +} +extension Version: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let versionString = try container.decode(String.self) + let components = versionString.components(separatedBy: ".") + guard components.count == 3 else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Malformed version.") + } + + guard let major = UInt(components[0]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Major invalid.") + } + + guard let minor = UInt(components[1]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Minor invalid.") + } + + guard let patch = UInt(components[2]) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Patch invalid.") + } + + self.major = major + self.minor = minor + self.patch = patch + } +} diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index 7c01a2ac..05f087c8 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -18,7 +18,7 @@ public final class SerializationTests: XCTestCase { let data = try encoder.encode(nexus) XCTAssertNotNil(data) - XCTAssertEqual(data.count, 307) + XCTAssertGreaterThanOrEqual(data.count, 307) let encoder2 = JSONEncoder() let data2 = try encoder2.encode(nexus) From 5ebf44a729e569d9d52e785a88e1dc4c8c1347c5 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 21 Aug 2020 10:50:54 +0200 Subject: [PATCH 12/23] Ensure stable entities over serialization --- .../SerializationTests.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index 05f087c8..d8c897e8 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -11,9 +11,18 @@ import FirebladeECS public final class SerializationTests: XCTestCase { func testSerialization() throws { let nexus = Nexus() + let e1 = nexus.createEntity() + let e2 = nexus.createEntity() nexus.createEntity(with: Position(x: 1, y: 4), Name(name: "myName")) + let e3 = nexus.createEntity() nexus.createEntity(with: Position(x: 5, y: 18), Name(name: "yourName")) + + // Fragment entities + nexus.destroy(entity: e2) + nexus.destroy(entity: e3) + nexus.destroy(entity: e1) + let encoder = JSONEncoder() let data = try encoder.encode(nexus) @@ -29,8 +38,16 @@ public final class SerializationTests: XCTestCase { func testDeserialization() throws { let nexus = Nexus() + let e1 = nexus.createEntity() + let e2 = nexus.createEntity() let firstEntity = nexus.createEntity(with: Name(name: "myName"), Position(x: 1, y: 2)) + let e3 = nexus.createEntity() let secondEntity = nexus.createEntity(with: Velocity(a: 3.14), Party(partying: true)) + + // Fragment entities + nexus.destroy(entity: e2) + nexus.destroy(entity: e3) + nexus.destroy(entity: e1) let encoder = JSONEncoder() let data = try encoder.encode(nexus) @@ -39,6 +56,7 @@ public final class SerializationTests: XCTestCase { let nexus2: Nexus = try decoder.decode(Nexus.self, from: data) let firstEntity2 = nexus2.get(entity: firstEntity.identifier)! + XCTAssertEqual(firstEntity2.identifier, firstEntity.identifier) XCTAssertTrue(firstEntity2.has(Name.self)) XCTAssertTrue(firstEntity2.has(Position.self)) XCTAssertEqual(firstEntity2.get(component: Name.self)?.name, "myName") @@ -46,6 +64,7 @@ public final class SerializationTests: XCTestCase { XCTAssertEqual(firstEntity2.get(component: Position.self)?.y, 2) let secondEntity2 = nexus2.get(entity: secondEntity.identifier)! + XCTAssertEqual(secondEntity2.identifier, secondEntity.identifier) XCTAssertTrue(secondEntity2.has(Velocity.self)) XCTAssertTrue(secondEntity2.has(Party.self)) XCTAssertEqual(secondEntity2.get(component: Velocity.self)?.a, 3.14) From 2bce2987f703e0fa4342bb9db0420e65a1eeedc5 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 21 Aug 2020 11:55:26 +0200 Subject: [PATCH 13/23] Use new entity id generator --- Sources/FirebladeECS/Nexus+Serialization.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index ec50a429..44c4a513 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -33,12 +33,12 @@ extension Nexus { } convenience init(from sNexus: SNexus) throws { - let entityIds = sNexus.entities.map { $0.key } + let entityIds = sNexus.entities.map { $0.key }.reversed() self.init(entityStorage: UnorderedSparseSet(), componentsByType: [:], componentsByEntity: [:], - entityIdGenerator: EntityIdentifierGenerator(entityIds), + entityIdGenerator: DefaultEntityIdGenerator(startProviding: entityIds), // FIXME: this does not respect the generator of the target nexus! familyMembersByTraits: [:], codingStrategy: DefaultCodingStrategy()) From 2e6ecdf104f22a0f48889ef279bff57b05c0dd5b Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 21 Aug 2020 12:07:46 +0200 Subject: [PATCH 14/23] Lint --- Sources/FirebladeECS/Nexus+Serialization.swift | 3 ++- Tests/FirebladeECSTests/SerializationTests.swift | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index 44c4a513..af789807 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -35,10 +35,11 @@ extension Nexus { convenience init(from sNexus: SNexus) throws { let entityIds = sNexus.entities.map { $0.key }.reversed() + // FIXME: this does not respect the generator of the target nexus! self.init(entityStorage: UnorderedSparseSet(), componentsByType: [:], componentsByEntity: [:], - entityIdGenerator: DefaultEntityIdGenerator(startProviding: entityIds), // FIXME: this does not respect the generator of the target nexus! + entityIdGenerator: DefaultEntityIdGenerator(startProviding: entityIds), familyMembersByTraits: [:], codingStrategy: DefaultCodingStrategy()) diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index d8c897e8..a69f2e33 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -17,12 +17,11 @@ public final class SerializationTests: XCTestCase { let e3 = nexus.createEntity() nexus.createEntity(with: Position(x: 5, y: 18), Name(name: "yourName")) - // Fragment entities nexus.destroy(entity: e2) nexus.destroy(entity: e3) nexus.destroy(entity: e1) - + let encoder = JSONEncoder() let data = try encoder.encode(nexus) @@ -43,7 +42,7 @@ public final class SerializationTests: XCTestCase { let firstEntity = nexus.createEntity(with: Name(name: "myName"), Position(x: 1, y: 2)) let e3 = nexus.createEntity() let secondEntity = nexus.createEntity(with: Velocity(a: 3.14), Party(partying: true)) - + // Fragment entities nexus.destroy(entity: e2) nexus.destroy(entity: e3) From 64aea76a8e0b3a7e886f82fe149c7b2f5c9861be Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Fri, 21 Aug 2020 16:37:40 +0200 Subject: [PATCH 15/23] Update to new storage --- Sources/FirebladeECS/ComponentIdentifier.swift | 1 + Sources/FirebladeECS/Nexus+Serialization.swift | 5 ++--- Tests/FirebladeECSTests/SerializationTests.swift | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/FirebladeECS/ComponentIdentifier.swift b/Sources/FirebladeECS/ComponentIdentifier.swift index 54a6da9b..10ac0dc1 100644 --- a/Sources/FirebladeECS/ComponentIdentifier.swift +++ b/Sources/FirebladeECS/ComponentIdentifier.swift @@ -22,6 +22,7 @@ extension ComponentIdentifier { ObjectIdentifier(componentType).hashValue } + typealias StableId = UInt64 internal static func makeStableTypeHash(component: Component) -> StableId { let componentTypeString = String(describing: type(of: component)) return StringHashing.singer_djb2(componentTypeString) diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index af789807..ba3e4d33 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -15,7 +15,7 @@ extension Nexus { var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] var entityComponentsMap: [EntityIdentifier: Set] = [:] - for entitId in self.entityStorage { + for entitId in self.componentIdsByEntity.keys { entityComponentsMap[entitId] = [] let componentIds = self.get(components: entitId) ?? [] @@ -36,8 +36,7 @@ extension Nexus { let entityIds = sNexus.entities.map { $0.key }.reversed() // FIXME: this does not respect the generator of the target nexus! - self.init(entityStorage: UnorderedSparseSet(), - componentsByType: [:], + self.init(componentsByType: [:], componentsByEntity: [:], entityIdGenerator: DefaultEntityIdGenerator(startProviding: entityIds), familyMembersByTraits: [:], diff --git a/Tests/FirebladeECSTests/SerializationTests.swift b/Tests/FirebladeECSTests/SerializationTests.swift index a69f2e33..b32d3d56 100644 --- a/Tests/FirebladeECSTests/SerializationTests.swift +++ b/Tests/FirebladeECSTests/SerializationTests.swift @@ -54,7 +54,7 @@ public final class SerializationTests: XCTestCase { let decoder = JSONDecoder() let nexus2: Nexus = try decoder.decode(Nexus.self, from: data) - let firstEntity2 = nexus2.get(entity: firstEntity.identifier)! + let firstEntity2 = nexus2.entity(from: firstEntity.identifier) XCTAssertEqual(firstEntity2.identifier, firstEntity.identifier) XCTAssertTrue(firstEntity2.has(Name.self)) XCTAssertTrue(firstEntity2.has(Position.self)) @@ -62,7 +62,7 @@ public final class SerializationTests: XCTestCase { XCTAssertEqual(firstEntity2.get(component: Position.self)?.x, 1) XCTAssertEqual(firstEntity2.get(component: Position.self)?.y, 2) - let secondEntity2 = nexus2.get(entity: secondEntity.identifier)! + let secondEntity2 = nexus2.entity(from: secondEntity.identifier) XCTAssertEqual(secondEntity2.identifier, secondEntity.identifier) XCTAssertTrue(secondEntity2.has(Velocity.self)) XCTAssertTrue(secondEntity2.has(Party.self)) From 2333282fd49cfd0e62a85c0abebc361f6753aa71 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 21 Oct 2020 07:33:38 +0200 Subject: [PATCH 16/23] Fix compile issue --- Sources/FirebladeECS/Nexus+Serialization.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index ba3e4d33..c2434b27 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -20,7 +20,7 @@ extension Nexus { let componentIds = self.get(components: entitId) ?? [] for componentId in componentIds { - let component = self.get(unsafeComponent: componentId, for: entitId) + let component = self.get(unsafe: componentId, for: entitId) let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) componentInstances[componentStableInstanceHash] = SComponent.component(component) entityComponentsMap[entitId]?.insert(componentStableInstanceHash) From 40d09a9c3bd54d41de202e553bdb85418e882a88 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 21 Oct 2020 08:15:15 +0200 Subject: [PATCH 17/23] Conform CodingStrategy and EntityIdentifierGenerator to Codable --- Sources/FirebladeECS/CodingStrategy.swift | 2 +- Sources/FirebladeECS/EntityIdentifierGenerator.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/FirebladeECS/CodingStrategy.swift b/Sources/FirebladeECS/CodingStrategy.swift index 5b898486..7f06ac63 100644 --- a/Sources/FirebladeECS/CodingStrategy.swift +++ b/Sources/FirebladeECS/CodingStrategy.swift @@ -5,7 +5,7 @@ // Created by Christian Treffs on 05.08.20. // -public protocol CodingStrategy { +public protocol CodingStrategy: Codable { func codingKey(for componentType: C.Type) -> DynamicCodingKey where C: Component } diff --git a/Sources/FirebladeECS/EntityIdentifierGenerator.swift b/Sources/FirebladeECS/EntityIdentifierGenerator.swift index 1c9a0356..345493db 100644 --- a/Sources/FirebladeECS/EntityIdentifierGenerator.swift +++ b/Sources/FirebladeECS/EntityIdentifierGenerator.swift @@ -11,7 +11,7 @@ /// It also allows entity ids to be marked as unused (to be re-usable). /// /// You should strive to keep entity ids tightly packed around `EntityIdentifier.Identifier.min` since it has an influence on the underlying memory layout. -public protocol EntityIdentifierGenerator { +public protocol EntityIdentifierGenerator: Codable { /// Initialize the generator providing entity ids to begin with when creating new entities. /// /// Entity ids provided should be passed to `nextId()` in last out order up until the collection is empty. @@ -101,3 +101,4 @@ public struct LinearIncrementingEntityIdGenerator: EntityIdentifierGenerator { storage.markUnused(entityId: entityId) } } +extension LinearIncrementingEntityIdGenerator.Storage: Codable { } From 168e1a8598f237040c342913096b4ad1f3df631a Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 21 Oct 2020 08:15:44 +0200 Subject: [PATCH 18/23] Refine Version --- Sources/FirebladeECS/Version.swift | 159 +++++++++++++++++++++++++---- 1 file changed, 138 insertions(+), 21 deletions(-) diff --git a/Sources/FirebladeECS/Version.swift b/Sources/FirebladeECS/Version.swift index bcb3f61a..47f5dc2e 100644 --- a/Sources/FirebladeECS/Version.swift +++ b/Sources/FirebladeECS/Version.swift @@ -5,42 +5,159 @@ // Created by Christian Treffs on 14.07.20. // +/// A struct representing a semantic version. +/// +/// See for details. public struct Version { + /// Major version. public let major: UInt + + /// Minor version. public let minor: UInt + + /// Patch version. public let patch: UInt + + /// Pre-release identifiers. + public let prereleaseIdentifiers: [String] + + /// Build metadata identifiers. + public let buildMetadataIdentifiers: [String] + + public init(_ major: UInt, _ minor: UInt, _ patch: UInt, prereleaseIdentifiers: [String] = [], buildMetadataIdentifiers: [String] = []) { + self.major = major + self.minor = minor + self.patch = patch + self.prereleaseIdentifiers = prereleaseIdentifiers + self.buildMetadataIdentifiers = buildMetadataIdentifiers + } + + public init?(decoding versionString: String) { + let prereleaseStartIndex = versionString.firstIndex(of: "-") + let metadataStartIndex = versionString.firstIndex(of: "+") + + let requiredEndIndex = prereleaseStartIndex ?? metadataStartIndex ?? versionString.endIndex + let requiredCharacters = versionString.prefix(upTo: requiredEndIndex) + let requiredComponents: [UInt] = requiredCharacters + .split(separator: ".", maxSplits: 2, omittingEmptySubsequences: false) + .map(String.init) + .compactMap(UInt.init) + + guard requiredComponents.count == 3 else { return nil } + + self.major = requiredComponents[0] + self.minor = requiredComponents[1] + self.patch = requiredComponents[2] + + func identifiers(start: String.Index?, end: String.Index) -> [String] { + guard let start = start else { return [] } + let identifiers = versionString[versionString.index(after: start).. Bool { + major == other.major && minor == other.minor && patch == other.patch } -} -extension Version: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let versionString = try container.decode(String.self) - let components = versionString.components(separatedBy: ".") - guard components.count == 3 else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Malformed version.") + + public static func < (lhs: Version, rhs: Version) -> Bool { + let lhsComparators = [lhs.major, lhs.minor, lhs.patch] + let rhsComparators = [rhs.major, rhs.minor, rhs.patch] + + if lhsComparators != rhsComparators { + return lhsComparators.lexicographicallyPrecedes(rhsComparators) } - guard let major = UInt(components[0]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Major invalid.") + guard lhs.prereleaseIdentifiers.count > 0 else { + return false // Non-prerelease lhs >= potentially prerelease rhs } - guard let minor = UInt(components[1]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Minor invalid.") + guard rhs.prereleaseIdentifiers.count > 0 else { + return true // Prerelease lhs < non-prerelease rhs } - guard let patch = UInt(components[2]) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Patch invalid.") + let zippedIdentifiers = zip(lhs.prereleaseIdentifiers, rhs.prereleaseIdentifiers) + for (lhsPrereleaseIdentifier, rhsPrereleaseIdentifier) in zippedIdentifiers { + if lhsPrereleaseIdentifier == rhsPrereleaseIdentifier { + continue + } + + let typedLhsIdentifier: Any = Int(lhsPrereleaseIdentifier) ?? lhsPrereleaseIdentifier + let typedRhsIdentifier: Any = Int(rhsPrereleaseIdentifier) ?? rhsPrereleaseIdentifier + + switch (typedLhsIdentifier, typedRhsIdentifier) { + case let (int1 as Int, int2 as Int): return int1 < int2 + case let (string1 as String, string2 as String): return string1 < string2 + case (is Int, is String): return true // Int prereleases < String prereleases + case (is String, is Int): return false + + default: + return false + } } - self.major = major - self.minor = minor - self.patch = patch + return lhs.prereleaseIdentifiers.count < rhs.prereleaseIdentifiers.count + } +} + +extension Version: ExpressibleByStringLiteral { + public init(stringLiteral versionString: String) { + guard let version = Version(decoding: versionString) else { + fatalError("Malformed version string '\(versionString)'.") + } + self = version + } +} + +extension Version: CustomStringConvertible { + public var description: String { + versionString + } +} + +extension Version: CustomDebugStringConvertible { + public var debugDescription: String { + versionString + } +} + +extension Version: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let versionString = try container.decode(String.self) + guard let version = Version(decoding: versionString) else { + throw DecodingError.dataCorruptedError(in: container, + debugDescription: "Malformed version string '\(versionString)'.") + } + + self = version + } +} + +extension Version: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(versionString) } } From dadfeb86b12d7682a0d06968f6b02668c8ef58ee Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 21 Oct 2020 08:16:26 +0200 Subject: [PATCH 19/23] Refine Nexus serialization --- Sources/FirebladeECS/Nexus+Codable.swift | 4 +++- Sources/FirebladeECS/Nexus+Serialization.swift | 16 ++++------------ Sources/FirebladeECS/Nexus.swift | 5 +++++ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Sources/FirebladeECS/Nexus+Codable.swift b/Sources/FirebladeECS/Nexus+Codable.swift index 377f54c0..8034f3bf 100644 --- a/Sources/FirebladeECS/Nexus+Codable.swift +++ b/Sources/FirebladeECS/Nexus+Codable.swift @@ -15,8 +15,10 @@ extension Nexus: Encodable { extension Nexus: Decodable { public convenience init(from decoder: Decoder) throws { + self.init() + let container = try decoder.singleValueContainer() let sNexus = try container.decode(SNexus.self) - try self.init(from: sNexus) + try deserialize(from: sNexus, into: self) } } diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index c2434b27..93e2934e 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -10,8 +10,6 @@ import struct Foundation.Data extension Nexus { final func serialize() throws -> SNexus { - let version = Version(major: 1, minor: 0, patch: 0) - var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] var entityComponentsMap: [EntityIdentifier: Set] = [:] @@ -32,21 +30,15 @@ extension Nexus { components: componentInstances) } - convenience init(from sNexus: SNexus) throws { - let entityIds = sNexus.entities.map { $0.key }.reversed() - - // FIXME: this does not respect the generator of the target nexus! - self.init(componentsByType: [:], - componentsByEntity: [:], - entityIdGenerator: DefaultEntityIdGenerator(startProviding: entityIds), - familyMembersByTraits: [:], - codingStrategy: DefaultCodingStrategy()) + final func deserialize(from sNexus: SNexus, into nexus: Nexus) throws { + for freeId in sNexus.entities.map { $0.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 { - // FIXME: we want a dedicated error throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Could not find component instance for \(sCompId).")) } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index 8f7c7f3a..a69a8563 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -6,6 +6,11 @@ // public final class Nexus { + /// The version of this Nexus implementation. + /// + /// Used for serialization. + final let version = Version(0, 18, 0) + /// - Key: ComponentIdentifier aka component type. /// - Value: Array of component instances of same type (uniform). /// New component instances are appended. From c4c3cce75605b3df31b83098ee7498e3be222f0c Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 21 Oct 2020 08:16:43 +0200 Subject: [PATCH 20/23] Lint --- Tests/FirebladeECSTests/Generated/FamilyTests.generated.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/FirebladeECSTests/Generated/FamilyTests.generated.swift b/Tests/FirebladeECSTests/Generated/FamilyTests.generated.swift index 8d49cd6a..3f0b9882 100644 --- a/Tests/FirebladeECSTests/Generated/FamilyTests.generated.swift +++ b/Tests/FirebladeECSTests/Generated/FamilyTests.generated.swift @@ -1476,3 +1476,4 @@ extension Comp8: Equatable { } } extension Comp8: Codable { } + From 699233a7072bca912dd34a41cef34814bb5b33df Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Mon, 21 Oct 2024 13:50:18 +0200 Subject: [PATCH 21/23] Fix warning --- Sources/FirebladeECS/Nexus+Serialization.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index 93e2934e..ee0f180f 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -31,7 +31,7 @@ extension Nexus { } final func deserialize(from sNexus: SNexus, into nexus: Nexus) throws { - for freeId in sNexus.entities.map { $0.key }.reversed() { + for freeId in sNexus.entities.map(\.key).reversed() { nexus.entityIdGenerator.markUnused(entityId: freeId) } From 041440c40dc2d6a81dbde691f18cde37fce633fe Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Mon, 21 Oct 2024 13:53:31 +0200 Subject: [PATCH 22/23] Remove test manifest --- Tests/FirebladeECSTests/XCTestManifests.swift | 465 ------------------ 1 file changed, 465 deletions(-) delete mode 100644 Tests/FirebladeECSTests/XCTestManifests.swift diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift deleted file mode 100644 index 52a81ab1..00000000 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ /dev/null @@ -1,465 +0,0 @@ -#if !canImport(ObjectiveC) -import XCTest - -extension ComponentIdentifierTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ComponentIdentifierTests = [ - ("testMirrorAsStableIdentifier", testMirrorAsStableIdentifier), - ("testStringDescribingAsStableIdentifier", testStringDescribingAsStableIdentifier), - ] -} - -extension ComponentInstanceProviderTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ComponentInstanceProviderTests = [ - ("testProviderReturnsTheInstance", testProviderReturnsTheInstance), - ("testProvidersWithDifferentInstanceHaveDifferentIdentifier", testProvidersWithDifferentInstanceHaveDifferentIdentifier), - ("testProvidersWithSameInstanceHaveSameIdentifier", testProvidersWithSameInstanceHaveSameIdentifier), - ] -} - -extension ComponentSingletonProviderTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ComponentSingletonProviderTests = [ - ("testProviderReturnsAnInstanceOfType", testProviderReturnsAnInstanceOfType), - ("testProviderReturnsSameInstanceEachTime", testProviderReturnsSameInstanceEachTime), - ("testProvidersWithDifferentTypeHaveDifferentIdentifier", testProvidersWithDifferentTypeHaveDifferentIdentifier), - ("testProvidersWithSameTypeHaveDifferentIdentifier", testProvidersWithSameTypeHaveDifferentIdentifier), - ] -} - -extension ComponentTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ComponentTests = [ - ("testComponentIdentifier", testComponentIdentifier), - ] -} - -extension ComponentTypeProviderTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ComponentTypeProviderTests = [ - ("testProviderReturnsAnInstanceOfType", testProviderReturnsAnInstanceOfType), - ("testProviderReturnsNewInstanceEachTime", testProviderReturnsNewInstanceEachTime), - ("testProvidersWithDifferentTypeHaveDifferentIdentifier", testProvidersWithDifferentTypeHaveDifferentIdentifier), - ("testProvidersWithSameTypeHaveSameIdentifier", testProvidersWithSameTypeHaveSameIdentifier), - ] -} - -extension DynamicComponentProviderTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__DynamicComponentProviderTests = [ - ("testProviderReturnsTheInstance", testProviderReturnsTheInstance), - ("testProvidersWithDifferentMethodsHaveDifferentIdentifier", testProvidersWithDifferentMethodsHaveDifferentIdentifier), - ("testProvidersWithSameMethodHaveSameIdentifier", testProvidersWithSameMethodHaveSameIdentifier), - ] -} - -extension EntityCreationTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__EntityCreationTests = [ - ("testBulkCreateEntitiesMultipleComponents", testBulkCreateEntitiesMultipleComponents), - ("testBulkCreateEntitiesOneComponent", testBulkCreateEntitiesOneComponent), - ("testCreateEntityMultipleComponents", testCreateEntityMultipleComponents), - ("testCreateEntityOneComponent", testCreateEntityOneComponent), - ] -} - -extension EntityIdGenTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__EntityIdGenTests = [ - ("testGenerateWithInitialIds", testGenerateWithInitialIds), - ("testGeneratorDefaultInit", testGeneratorDefaultInit), - ("testGeneratorMarkUnused", testGeneratorMarkUnused), - ("testGeneratorWithDefaultEmptyCollection", testGeneratorWithDefaultEmptyCollection), - ("testLinearIncrement", testLinearIncrement), - ] -} - -extension EntityStateMachineTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__EntityStateMachineTests = [ - ("testCallChangeStateWithSameNameLeavesEntityComponentsIntact", testCallChangeStateWithSameNameLeavesEntityComponentsIntact), - ("testCreateStateAddsState", testCreateStateAddsState), - ("testCreateStateDoesNotChangeState", testCreateStateDoesNotChangeState), - ("testEnterSecondStateAddsSecondStatesComponents", testEnterSecondStateAddsSecondStatesComponents), - ("testEnterSecondStateDoesNotRemoveOverlappingComponents", testEnterSecondStateDoesNotRemoveOverlappingComponents), - ("testEnterSecondStateRemovesDifferentComponentsOfSameType", testEnterSecondStateRemovesDifferentComponentsOfSameType), - ("testEnterSecondStateRemovesFirstStatesComponents", testEnterSecondStateRemovesFirstStatesComponents), - ("testEnterStateAddsStatesComponents", testEnterStateAddsStatesComponents), - ("testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity", testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity), - ] -} - -extension EntityStateTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__EntityStateTests = [ - ("testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType", testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType), - ("testAddMappingWithInstanceQualifierCreatesInstanceProvider", testAddMappingWithInstanceQualifierCreatesInstanceProvider), - ("testAddMappingWithMethodQualifierCreatesDynamicProvider", testAddMappingWithMethodQualifierCreatesDynamicProvider), - ("testAddMappingWithNoQualifierCreatesTypeProvider", testAddMappingWithNoQualifierCreatesTypeProvider), - ("testAddMappingWithSingletonQualifierCreatesSingletonProvider", testAddMappingWithSingletonQualifierCreatesSingletonProvider), - ("testAddMappingWithTypeQualifierCreatesTypeProvider", testAddMappingWithTypeQualifierCreatesTypeProvider), - ("testAddMethodCreatesMappingAndSetsDynamicProviderForType", testAddMethodCreatesMappingAndSetsDynamicProviderForType), - ("testAddProviderCreatesMappingAndSetsProvider", testAddProviderCreatesMappingAndSetsProvider), - ("testAddSingletonCreatesMappingAndSetsSingletonProviderForType", testAddSingletonCreatesMappingAndSetsSingletonProviderForType), - ("testAddTypeCreatesMappingAndSetsTypeProviderForType", testAddTypeCreatesMappingAndSetsTypeProviderForType), - ("testHasProviderReturnsFalseForNotCreatedProvider", testHasProviderReturnsFalseForNotCreatedProvider), - ("testHasProviderReturnsTrueForCreatedProvider", testHasProviderReturnsTrueForCreatedProvider), - ("testProviderForTypeReturnsDynamicProvider", testProviderForTypeReturnsDynamicProvider), - ("testProviderForTypeReturnsInstanceProvider", testProviderForTypeReturnsInstanceProvider), - ("testProviderForTypeReturnsPassedProvider", testProviderForTypeReturnsPassedProvider), - ("testProviderForTypeReturnsSingletonProvider", testProviderForTypeReturnsSingletonProvider), - ("testProviderForTypeReturnsTypeProvider", testProviderForTypeReturnsTypeProvider), - ("testProviderForTypeReturnsTypeProviderByDefault", testProviderForTypeReturnsTypeProviderByDefault), - ] -} - -extension EntityTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__EntityTests = [ - ("testAllComponentsOfEntity", testAllComponentsOfEntity), - ("testComponentsIteration", testComponentsIteration), - ("testEntityCreationIntrinsic", testEntityCreationIntrinsic), - ("testEntityDescriptions", testEntityDescriptions), - ("testEntityEquality", testEntityEquality), - ("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex), - ("testEntityIdGenerator", testEntityIdGenerator), - ("testEntitySubscripts", testEntitySubscripts), - ("testRemoveAllComponentsFromEntity", testRemoveAllComponentsFromEntity), - ] -} - -extension Family1Tests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__Family1Tests = [ - ("testComponentIteration", testComponentIteration), - ("testEntityComponentIteration", testEntityComponentIteration), - ("testEntityIteration", testEntityIteration), - ("testFamilyDecoding", testFamilyDecoding), - ("testFamilyEncoding", testFamilyEncoding), - ("testFamilyFailDecoding", testFamilyFailDecoding), - ("testMemberCreation", testMemberCreation), - ("testMemberCreationBuilder", testMemberCreationBuilder), - ] -} - -extension Family2Tests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__Family2Tests = [ - ("testComponentIteration", testComponentIteration), - ("testEntityComponentIteration", testEntityComponentIteration), - ("testEntityIteration", testEntityIteration), - ("testFamilyDecoding", testFamilyDecoding), - ("testFamilyEncoding", testFamilyEncoding), - ("testFamilyFailDecoding", testFamilyFailDecoding), - ("testMemberCreation", testMemberCreation), - ("testMemberCreationBuilder", testMemberCreationBuilder), - ] -} - -extension Family3Tests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__Family3Tests = [ - ("testComponentIteration", testComponentIteration), - ("testEntityComponentIteration", testEntityComponentIteration), - ("testEntityIteration", testEntityIteration), - ("testFamilyDecoding", testFamilyDecoding), - ("testFamilyEncoding", testFamilyEncoding), - ("testFamilyFailDecoding", testFamilyFailDecoding), - ("testMemberCreation", testMemberCreation), - ("testMemberCreationBuilder", testMemberCreationBuilder), - ] -} - -extension Family4Tests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__Family4Tests = [ - ("testComponentIteration", testComponentIteration), - ("testEntityComponentIteration", testEntityComponentIteration), - ("testEntityIteration", testEntityIteration), - ("testFamilyDecoding", testFamilyDecoding), - ("testFamilyEncoding", testFamilyEncoding), - ("testFamilyFailDecoding", testFamilyFailDecoding), - ("testMemberCreation", testMemberCreation), - ("testMemberCreationBuilder", testMemberCreationBuilder), - ] -} - -extension Family5Tests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__Family5Tests = [ - ("testComponentIteration", testComponentIteration), - ("testEntityComponentIteration", testEntityComponentIteration), - ("testEntityIteration", testEntityIteration), - ("testFamilyDecoding", testFamilyDecoding), - ("testFamilyEncoding", testFamilyEncoding), - ("testFamilyFailDecoding", testFamilyFailDecoding), - ("testMemberCreation", testMemberCreation), - ("testMemberCreationBuilder", testMemberCreationBuilder), - ] -} - -extension Family6Tests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__Family6Tests = [ - ("testComponentIteration", testComponentIteration), - ("testEntityComponentIteration", testEntityComponentIteration), - ("testEntityIteration", testEntityIteration), - ("testFamilyDecoding", testFamilyDecoding), - ("testFamilyEncoding", testFamilyEncoding), - ("testFamilyFailDecoding", testFamilyFailDecoding), - ("testMemberCreation", testMemberCreation), - ("testMemberCreationBuilder", testMemberCreationBuilder), - ] -} - -extension Family7Tests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__Family7Tests = [ - ("testComponentIteration", testComponentIteration), - ("testEntityComponentIteration", testEntityComponentIteration), - ("testEntityIteration", testEntityIteration), - ("testFamilyDecoding", testFamilyDecoding), - ("testFamilyEncoding", testFamilyEncoding), - ("testFamilyFailDecoding", testFamilyFailDecoding), - ("testMemberCreation", testMemberCreation), - ("testMemberCreationBuilder", testMemberCreationBuilder), - ] -} - -extension Family8Tests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__Family8Tests = [ - ("testComponentIteration", testComponentIteration), - ("testEntityComponentIteration", testEntityComponentIteration), - ("testEntityIteration", testEntityIteration), - ("testFamilyDecoding", testFamilyDecoding), - ("testFamilyEncoding", testFamilyEncoding), - ("testFamilyFailDecoding", testFamilyFailDecoding), - ("testMemberCreation", testMemberCreation), - ("testMemberCreationBuilder", testMemberCreationBuilder), - ] -} - -extension FamilyCodingTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FamilyCodingTests = [ - ("testDecodeFamily4", testDecodeFamily4), - ("testDecodeFamily5", testDecodeFamily5), - ("testDecodingFamily1", testDecodingFamily1), - ("testDecodingFamily2", testDecodingFamily2), - ("testDecodingFamily3", testDecodingFamily3), - ("testEncodeFamily2", testEncodeFamily2), - ("testEncodeFamily3", testEncodeFamily3), - ("testEncodeFamily4", testEncodeFamily4), - ("testEncodeFamily5", testEncodeFamily5), - ("testEncodingFamily1", testEncodingFamily1), - ("testFailDecodingFamily", testFailDecodingFamily), - ] -} - -extension FamilyTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FamilyTests = [ - ("testFamilyAbandoned", testFamilyAbandoned), - ("testFamilyBulkDestroy", testFamilyBulkDestroy), - ("testFamilyCreateMembers", testFamilyCreateMembers), - ("testFamilyCreation", testFamilyCreation), - ("testFamilyDestroyMembers", testFamilyDestroyMembers), - ("testFamilyExchange", testFamilyExchange), - ("testFamilyLateMember", testFamilyLateMember), - ("testFamilyMemberBasicIteration", testFamilyMemberBasicIteration), - ("testFamilyReuse", testFamilyReuse), - ] -} - -extension FamilyTraitsTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FamilyTraitsTests = [ - ("testTraitCommutativity", testTraitCommutativity), - ("testTraitMatching", testTraitMatching), - ] -} - -extension HashingTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__HashingTests = [ - ("testCollisionsInCritialRange", testCollisionsInCritialRange), - ("testStringHashes", testStringHashes), - ] -} - -extension NexusEventDelegateTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__NexusEventDelegateTests = [ - ("testEventComponentAdded", testEventComponentAdded), - ("testEventComponentRemoved", testEventComponentRemoved), - ("testEventEntityCreated", testEventEntityCreated), - ("testEventEntityDestroyed", testEventEntityDestroyed), - ("testFamilyMemberRemoved", testFamilyMemberRemoved), - ("testFamilyMemeberAdded", testFamilyMemeberAdded), - ] -} - -extension NexusTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__NexusTests = [ - ("testComponentCreation", testComponentCreation), - ("testComponentDeletion", testComponentDeletion), - ("testComponentRetrieval", testComponentRetrieval), - ("testComponentUniqueness", testComponentUniqueness), - ("testEntityCreate", testEntityCreate), - ("testEntityDestroy", testEntityDestroy), - ("testEntityIteration", testEntityIteration), - ] -} - -extension SerializationTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SerializationTests = [ - ("testDeserialization", testDeserialization), - ("testSerialization", testSerialization) - ] -} - -extension SingleTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SingleTests = [ - ("testSingleCreation", testSingleCreation), - ("testSingleCreationOnExistingFamilyMember", testSingleCreationOnExistingFamilyMember), - ("testSingleEntityAndComponentCreation", testSingleEntityAndComponentCreation), - ("testSingleReuse", testSingleReuse), - ] -} - -extension SparseSetTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SparseSetTests = [ - ("testAlternativeKey", testAlternativeKey), - ("testEquality", testEquality), - ("testSparseSetAdd", testSparseSetAdd), - ("testSparseSetAddAndReplace", testSparseSetAddAndReplace), - ("testSparseSetClear", testSparseSetClear), - ("testSparseSetDoubleRemove", testSparseSetDoubleRemove), - ("testSparseSetGet", testSparseSetGet), - ("testSparseSetNonCongiuousData", testSparseSetNonCongiuousData), - ("testSparseSetReduce", testSparseSetReduce), - ("testSparseSetRemove", testSparseSetRemove), - ("testSparseSetRemoveAndAdd", testSparseSetRemoveAndAdd), - ("testSparseSetRemoveNonPresent", testSparseSetRemoveNonPresent), - ("testStartEndIndex", testStartEndIndex), - ("testSubscript", testSubscript), - ] -} - -extension StateComponentMappingTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__StateComponentMappingTests = [ - ("testAddAddsProviderToState", testAddAddsProviderToState), - ("testAddReturnsSameMappingForDifferentComponentTypes", testAddReturnsSameMappingForDifferentComponentTypes), - ("testAddReturnsSameMappingForSameComponentType", testAddReturnsSameMappingForSameComponentType), - ] -} - -extension SystemsTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SystemsTests = [ - ("testSystemsUpdate", testSystemsUpdate), - ] -} - -public func __allTests() -> [XCTestCaseEntry] { - return [ - testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests), - testCase(ComponentInstanceProviderTests.__allTests__ComponentInstanceProviderTests), - testCase(ComponentSingletonProviderTests.__allTests__ComponentSingletonProviderTests), - testCase(ComponentTests.__allTests__ComponentTests), - testCase(ComponentTypeProviderTests.__allTests__ComponentTypeProviderTests), - testCase(DynamicComponentProviderTests.__allTests__DynamicComponentProviderTests), - testCase(EntityCreationTests.__allTests__EntityCreationTests), - testCase(EntityIdGenTests.__allTests__EntityIdGenTests), - testCase(EntityStateMachineTests.__allTests__EntityStateMachineTests), - testCase(EntityStateTests.__allTests__EntityStateTests), - testCase(EntityTests.__allTests__EntityTests), - testCase(Family1Tests.__allTests__Family1Tests), - testCase(Family2Tests.__allTests__Family2Tests), - testCase(Family3Tests.__allTests__Family3Tests), - testCase(Family4Tests.__allTests__Family4Tests), - testCase(Family5Tests.__allTests__Family5Tests), - testCase(Family6Tests.__allTests__Family6Tests), - testCase(Family7Tests.__allTests__Family7Tests), - testCase(Family8Tests.__allTests__Family8Tests), - testCase(FamilyCodingTests.__allTests__FamilyCodingTests), - testCase(FamilyTests.__allTests__FamilyTests), - testCase(FamilyTraitsTests.__allTests__FamilyTraitsTests), - testCase(HashingTests.__allTests__HashingTests), - testCase(NexusEventDelegateTests.__allTests__NexusEventDelegateTests), - testCase(NexusTests.__allTests__NexusTests), - testCase(SerializationTests.__allTests__SerializationTests), - testCase(SingleTests.__allTests__SingleTests), - testCase(SparseSetTests.__allTests__SparseSetTests), - testCase(StateComponentMappingTests.__allTests__StateComponentMappingTests), - testCase(SystemsTests.__allTests__SystemsTests), - ] -} -#endif From cae5c71af92e1ec1c372eb658fc0f1263be823f1 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Mon, 21 Oct 2024 13:54:11 +0200 Subject: [PATCH 23/23] Lint fix --- .../FirebladeECS/ComponentIdentifier.swift | 4 +- .../EntityIdentifierGenerator.swift | 3 +- Sources/FirebladeECS/Nexus+Codable.swift | 2 +- .../FirebladeECS/Nexus+Serialization.swift | 187 +++++++++--------- Sources/FirebladeECS/Version.swift | 27 +-- 5 files changed, 114 insertions(+), 109 deletions(-) diff --git a/Sources/FirebladeECS/ComponentIdentifier.swift b/Sources/FirebladeECS/ComponentIdentifier.swift index 9bf89055..09ee15f7 100644 --- a/Sources/FirebladeECS/ComponentIdentifier.swift +++ b/Sources/FirebladeECS/ComponentIdentifier.swift @@ -23,12 +23,12 @@ extension ComponentIdentifier { } typealias StableId = UInt64 - internal static func makeStableTypeHash(component: Component) -> StableId { + static func makeStableTypeHash(component: Component) -> StableId { let componentTypeString = String(describing: type(of: component)) 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) return StringHashing.singer_djb2(componentTypeString) } diff --git a/Sources/FirebladeECS/EntityIdentifierGenerator.swift b/Sources/FirebladeECS/EntityIdentifierGenerator.swift index 9d5f6bce..d92a7256 100644 --- a/Sources/FirebladeECS/EntityIdentifierGenerator.swift +++ b/Sources/FirebladeECS/EntityIdentifierGenerator.swift @@ -101,4 +101,5 @@ public struct LinearIncrementingEntityIdGenerator: EntityIdentifierGenerator { storage.markUnused(entityId: entityId) } } -extension LinearIncrementingEntityIdGenerator.Storage: Codable { } + +extension LinearIncrementingEntityIdGenerator.Storage: Codable {} diff --git a/Sources/FirebladeECS/Nexus+Codable.swift b/Sources/FirebladeECS/Nexus+Codable.swift index 8034f3bf..f00c83b9 100644 --- a/Sources/FirebladeECS/Nexus+Codable.swift +++ b/Sources/FirebladeECS/Nexus+Codable.swift @@ -7,7 +7,7 @@ extension Nexus: Encodable { public func encode(to encoder: Encoder) throws { - let serializedNexus = try self.serialize() + let serializedNexus = try serialize() var container = encoder.singleValueContainer() try container.encode(serializedNexus) } diff --git a/Sources/FirebladeECS/Nexus+Serialization.swift b/Sources/FirebladeECS/Nexus+Serialization.swift index ee0f180f..c9073b4a 100644 --- a/Sources/FirebladeECS/Nexus+Serialization.swift +++ b/Sources/FirebladeECS/Nexus+Serialization.swift @@ -6,119 +6,122 @@ // #if canImport(Foundation) -import struct Foundation.Data - -extension Nexus { - final func serialize() throws -> SNexus { - var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] - var entityComponentsMap: [EntityIdentifier: Set] = [:] - - for entitId in self.componentIdsByEntity.keys { - entityComponentsMap[entitId] = [] - let componentIds = self.get(components: entitId) ?? [] - - for componentId in componentIds { - let component = self.get(unsafe: componentId, for: entitId) - let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) - componentInstances[componentStableInstanceHash] = SComponent.component(component) - entityComponentsMap[entitId]?.insert(componentStableInstanceHash) + import struct Foundation.Data + + extension Nexus { + final func serialize() throws -> SNexus { + var componentInstances: [ComponentIdentifier.StableId: SComponent] = [:] + var entityComponentsMap: [EntityIdentifier: Set] = [:] + + for entitId in componentIdsByEntity.keys { + entityComponentsMap[entitId] = [] + let componentIds = self.get(components: entitId) ?? [] + + for componentId in componentIds { + let component = self.get(unsafe: componentId, for: entitId) + let componentStableInstanceHash = ComponentIdentifier.makeStableInstanceHash(component: component, entityId: entitId) + componentInstances[componentStableInstanceHash] = SComponent.component(component) + 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) + return SNexus(version: version, + entities: entityComponentsMap, + components: componentInstances) } - 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).")) - } + final func deserialize(from sNexus: SNexus, into nexus: Nexus) throws { + for freeId in sNexus.entities.map(\.key).reversed() { + nexus.entityIdGenerator.markUnused(entityId: freeId) + } - switch sComp { - case let .component(comp): - entity.assign(comp) + 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 { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(id) + extension EntityIdentifier: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + 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) + } + } + + struct SNexus { + let version: Version + let entities: [EntityIdentifier: Set] + let components: [ComponentIdentifier.StableId: SComponent] } -} -extension EntityIdentifier: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let id = try container.decode(UInt32.self) - self.init(id) + + 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 } -} - -internal struct SNexus { - let version: Version - let entities: [EntityIdentifier: Set] - let components: [ComponentIdentifier.StableId: SComponent] -} -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)) + + 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) } - 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) + + 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 { - case component(Component) -} + enum SComponent { + 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: 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 = .component(try CodingStrategy.decode(from: decoder)) + extension SComponent: Decodable { + public init(from decoder: Decoder) throws { + self = try .component(CodingStrategy.decode(from: decoder)) + } } -} #endif diff --git a/Sources/FirebladeECS/Version.swift b/Sources/FirebladeECS/Version.swift index 47f5dc2e..f7f8a83e 100644 --- a/Sources/FirebladeECS/Version.swift +++ b/Sources/FirebladeECS/Version.swift @@ -45,22 +45,24 @@ public struct Version { guard requiredComponents.count == 3 else { return nil } - self.major = requiredComponents[0] - self.minor = requiredComponents[1] - self.patch = requiredComponents[2] + major = requiredComponents[0] + minor = requiredComponents[1] + patch = requiredComponents[2] func identifiers(start: String.Index?, end: String.Index) -> [String] { - guard let start = start else { return [] } - let identifiers = versionString[versionString.index(after: start).. Bool { major == other.major && minor == other.minor && patch == other.patch @@ -89,11 +91,11 @@ extension Version: Comparable { return lhsComparators.lexicographicallyPrecedes(rhsComparators) } - guard lhs.prereleaseIdentifiers.count > 0 else { + guard !lhs.prereleaseIdentifiers.isEmpty else { 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 } @@ -111,7 +113,6 @@ extension Version: Comparable { case let (string1 as String, string2 as String): return string1 < string2 case (is Int, is String): return true // Int prereleases < String prereleases case (is String, is Int): return false - default: return false }