From f48fd3c2e7ca5e3e2df3a7ef03234396194c50d3 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Tue, 21 Jul 2020 17:02:16 +0200 Subject: [PATCH 1/9] Add createMember() method to families --- Sources/FirebladeECS/Family.swift | 14 ++++++++++++++ Sources/FirebladeECS/Family1.swift | 4 ++++ Sources/FirebladeECS/Family2.swift | 5 +++++ Sources/FirebladeECS/Family3.swift | 4 ++++ Sources/FirebladeECS/Family4.swift | 4 ++++ Sources/FirebladeECS/Family5.swift | 4 ++++ .../FirebladeECS/FamilyRequirementsManaging.swift | 2 ++ 7 files changed, 37 insertions(+) diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index b9df771..6310d3c 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -184,3 +184,17 @@ extension Family { } extension Family.RelativesIterator: LazySequenceProtocol { } + +// MARK: - member creation +extension Family { + /// Create a new entity with components required by this family. + /// + /// Since the created entity will meet the requirements of this family it + /// will automatically become member of this family. + /// - Parameter components: The components required by this family. + /// - Returns: The newly created entity. + @discardableResult + public func createMember(with components: R.Components) -> Entity { + R.createMember(nexus: nexus, components: components) + } +} diff --git a/Sources/FirebladeECS/Family1.swift b/Sources/FirebladeECS/Family1.swift index cc15d8d..b9b8be7 100644 --- a/Sources/FirebladeECS/Family1.swift +++ b/Sources/FirebladeECS/Family1.swift @@ -30,6 +30,10 @@ public struct Requires1: FamilyRequirementsManaging where A: Component { let childCompA: A = nexus.get(unsafeComponentFor: childId) return (parent: parentCompA, child: childCompA) } + + public static func createMember(nexus: Nexus, components: A) -> Entity { + nexus.createEntity(with: components) + } } extension Nexus { diff --git a/Sources/FirebladeECS/Family2.swift b/Sources/FirebladeECS/Family2.swift index 388002f..8aad060 100644 --- a/Sources/FirebladeECS/Family2.swift +++ b/Sources/FirebladeECS/Family2.swift @@ -15,6 +15,7 @@ public struct Requires2: FamilyRequirementsManaging where A: Component, B: public init(_ components: (A.Type, B.Type)) { componentTypes = [ A.self, B.self] } + public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (A, B) { let compA: A = nexus.get(unsafeComponentFor: entityId) let compB: B = nexus.get(unsafeComponentFor: entityId) @@ -35,6 +36,10 @@ public struct Requires2: FamilyRequirementsManaging where A: Component, B: let ccB: B = nexus.get(unsafeComponentFor: childId) return (parent: (pcA, pcB), child: (ccA, ccB)) } + + public static func createMember(nexus: Nexus, components: (A, B)) -> Entity { + nexus.createEntity(with: components.0, components.1) + } } extension Nexus { diff --git a/Sources/FirebladeECS/Family3.swift b/Sources/FirebladeECS/Family3.swift index bc3b941..0bfde27 100644 --- a/Sources/FirebladeECS/Family3.swift +++ b/Sources/FirebladeECS/Family3.swift @@ -40,6 +40,10 @@ public struct Requires3: FamilyRequirementsManaging where A: Component, let ccC: C = nexus.get(unsafeComponentFor: childId) return (parent: (pcA, pcB, pcC), child: (ccA, ccB, ccC)) } + + public static func createMember(nexus: Nexus, components: (A, B, C)) -> Entity { + nexus.createEntity(with: components.0, components.1, components.2) + } } extension Nexus { diff --git a/Sources/FirebladeECS/Family4.swift b/Sources/FirebladeECS/Family4.swift index f6ab1a6..17a0189 100644 --- a/Sources/FirebladeECS/Family4.swift +++ b/Sources/FirebladeECS/Family4.swift @@ -45,6 +45,10 @@ public struct Requires4: FamilyRequirementsManaging where A: Compone let ccD: D = nexus.get(unsafeComponentFor: childId) return (parent: (pcA, pcB, pcC, pcD), child: (ccA, ccB, ccC, ccD)) } + + public static func createMember(nexus: Nexus, components: (A, B, C, D)) -> Entity { + nexus.createEntity(with: components.0, components.1, components.2, components.3) + } } extension Nexus { diff --git a/Sources/FirebladeECS/Family5.swift b/Sources/FirebladeECS/Family5.swift index 02ee245..a4e8422 100644 --- a/Sources/FirebladeECS/Family5.swift +++ b/Sources/FirebladeECS/Family5.swift @@ -50,6 +50,10 @@ public struct Requires5: FamilyRequirementsManaging where A: Comp return (parent: (pcA, pcB, pcC, pcD, pcE), child: (ccA, ccB, ccC, ccD, ccE)) } + + public static func createMember(nexus: Nexus, components: (A, B, C, D, E)) -> Entity { + nexus.createEntity(with: components.0, components.1, components.2, components.3, components.4) + } } extension Nexus { diff --git a/Sources/FirebladeECS/FamilyRequirementsManaging.swift b/Sources/FirebladeECS/FamilyRequirementsManaging.swift index 9774563..b3605f4 100644 --- a/Sources/FirebladeECS/FamilyRequirementsManaging.swift +++ b/Sources/FirebladeECS/FamilyRequirementsManaging.swift @@ -18,4 +18,6 @@ public protocol FamilyRequirementsManaging { static func components(nexus: Nexus, entityId: EntityIdentifier) -> Components static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> EntityAndComponents static func relativesDescending(nexus: Nexus, parentId: EntityIdentifier, childId: EntityIdentifier) -> RelativesDescending + + static func createMember(nexus: Nexus, components: Components) -> Entity } From 4ed52db56c7f606fe0e58980f17273e7abf2d5f1 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Tue, 21 Jul 2020 17:12:20 +0200 Subject: [PATCH 2/9] Add createMember test --- Tests/FirebladeECSTests/FamilyTests.swift | 33 +++++++++++++++++++ Tests/FirebladeECSTests/XCTestManifests.swift | 1 + 2 files changed, 34 insertions(+) diff --git a/Tests/FirebladeECSTests/FamilyTests.swift b/Tests/FirebladeECSTests/FamilyTests.swift index 98dcfe1..4a709ea 100644 --- a/Tests/FirebladeECSTests/FamilyTests.swift +++ b/Tests/FirebladeECSTests/FamilyTests.swift @@ -184,4 +184,37 @@ class FamilyTests: XCTestCase { XCTAssertEqual(family.memberIds.count, count + (count / 2)) } + + func testFamilyCreateMembers() { + let position = Position(x: 0, y: 1) + let name = Name(name: "SomeName") + let velocity = Velocity(a: 123) + let party = Party(partying: true) + let color = Color() + + let family1 = nexus.family(requires: Position.self, excludesAll: Name.self) + XCTAssertTrue(family1.isEmpty) + family1.createMember(with: position) + XCTAssertEqual(family1.count, 1) + + let family2 = nexus.family(requiresAll: Position.self, Name.self) + XCTAssertTrue(family2.isEmpty) + family2.createMember(with: (position, name)) + XCTAssertEqual(family2.count, 1) + + let family3 = nexus.family(requiresAll: Position.self, Name.self, Velocity.self) + XCTAssertTrue(family3.isEmpty) + family3.createMember(with: (position, name, velocity)) + XCTAssertEqual(family3.count, 1) + + let family4 = nexus.family(requiresAll: Position.self, Name.self, Velocity.self, Party.self) + XCTAssertTrue(family4.isEmpty) + family4.createMember(with: (position, name, velocity, party)) + XCTAssertEqual(family4.count, 1) + + let family5 = nexus.family(requiresAll: Position.self, Name.self, Velocity.self, Party.self, Color.self) + XCTAssertTrue(family5.isEmpty) + family5.createMember(with: (position, name, velocity, party, color)) + XCTAssertEqual(family5.count, 1) + } } diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index db9521b..a1100e4 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -41,6 +41,7 @@ extension FamilyTests { static let __allTests__FamilyTests = [ ("testFamilyAbandoned", testFamilyAbandoned), ("testFamilyBulkDestroy", testFamilyBulkDestroy), + ("testFamilyCreateMembers", testFamilyCreateMembers), ("testFamilyCreation", testFamilyCreation), ("testFamilyExchange", testFamilyExchange), ("testFamilyLateMember", testFamilyLateMember), From 6812e53d7a81640bd68c5e52d6602d8c3936e005 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 22 Jul 2020 11:37:30 +0200 Subject: [PATCH 3/9] WIP: decoding + encoding --- Sources/FirebladeECS/Family+Codable.swift | 101 +++++++++++++++++ Sources/FirebladeECS/Family1.swift | 12 +++ Sources/FirebladeECS/Family2.swift | 15 +++ Sources/FirebladeECS/FamilyProtocols.swift | 69 ++++++++++++ .../FamilyRequirementsManaging.swift | 23 ---- Sources/FirebladeECS/Nexus.swift | 18 +++- Tests/FirebladeECSTests/Base.swift | 22 ++++ .../FirebladeECSTests/FamilyCodingTests.swift | 102 ++++++++++++++++++ Tests/FirebladeECSTests/XCTestManifests.swift | 13 +++ 9 files changed, 350 insertions(+), 25 deletions(-) create mode 100644 Sources/FirebladeECS/Family+Codable.swift create mode 100644 Sources/FirebladeECS/FamilyProtocols.swift delete mode 100644 Sources/FirebladeECS/FamilyRequirementsManaging.swift create mode 100644 Tests/FirebladeECSTests/FamilyCodingTests.swift diff --git a/Sources/FirebladeECS/Family+Codable.swift b/Sources/FirebladeECS/Family+Codable.swift new file mode 100644 index 0000000..050c2c0 --- /dev/null +++ b/Sources/FirebladeECS/Family+Codable.swift @@ -0,0 +1,101 @@ +// +// Family+Codable.swift +// +// +// Created by Christian Treffs on 22.07.20. +// + +private struct FamilyMemberContainer where R: FamilyRequirementsManaging { + let components: [R.Components] +} + +extension FamilyMemberContainer: Decodable where R: FamilyDecoding { + init(from decoder: Decoder) throws { + var familyContainer = try decoder.unkeyedContainer() + let strategy = decoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy() + self.components = try R.decode(componentsIn: &familyContainer, using: strategy) + } +} + +extension FamilyMemberContainer: Encodable where R: FamilyEncoding { + func encode(to encoder: Encoder) throws { + let strategy = encoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy() + var familyContainer = encoder.unkeyedContainer() + try R.encode(components: components, into: &familyContainer, using: strategy) + } +} + +// MARK: - encoding +public protocol TopLevelEncoder { + /// The type this encoder produces. + associatedtype Output + + /// Encodes an instance of the indicated type. + /// + /// - Parameter value: The instance to encode. + func encode(_ value: T) throws -> Self.Output where T: Encodable + + /// Contextual user-provided information for use during decoding. + var userInfo: [CodingUserInfoKey: Any] { get set } +} + +extension Family where R: FamilyEncoding { + /// Encode family members (entities) to data using a given encoder. + /// + /// The encoded members will *NOT* be removed from the nexus and will also stay present in this family. + /// - Parameter encoder: The data encoder. Data encoder respects the coding strategy set at `nexus.codingStrategy`. + /// - Returns: The encoded data. + public func encodeMembers(using encoder: inout Encoder) throws -> Encoder.Output where Encoder: TopLevelEncoder { + encoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy + let components: [R.Components] = self.map { $0 } + let container = FamilyMemberContainer(components: components) + return try encoder.encode(container) + } +} + +// MARK: - decoding +public protocol TopLevelDecoder { + /// The type this decoder accepts. + associatedtype Input + + /// Decodes an instance of the indicated type. + func decode(_ type: T.Type, from: Self.Input) throws -> T where T: Decodable + + /// Contextual user-provided information for use during decoding. + var userInfo: [CodingUserInfoKey: Any] { get set } +} + +extension Family where R: FamilyDecoding { + /// Decode family members (entities) from given data using a decoder. + /// + /// The decoded members will be added to the nexus and will be present in this family. + /// - Parameters: + /// - data: The data decoded by decoder. A unkeyed container of family members (keyed component containers) is expected. + /// - decoder: The decoder to use for decoding family member data. Decoder respects the coding strategy set at `nexus.codingStrategy`. + public func decodeMembers(from data: Decoder.Input, using decoder: inout Decoder) throws where Decoder: TopLevelDecoder { + decoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy + let familyMembers = try decoder.decode(FamilyMemberContainer.self, from: data) + for components in familyMembers.components { + createMember(with: components) + } + } +} + +extension CodingUserInfoKey { + fileprivate static let nexusCodingStrategy = CodingUserInfoKey(rawValue: "nexusCodingStrategy")! +} + +// MARK: - foundation +#if canImport(Foundation) +import class Foundation.JSONEncoder +import class Foundation.JSONDecoder + +import class Foundation.PropertyListEncoder +import class Foundation.PropertyListDecoder + +extension JSONEncoder: TopLevelEncoder { } +extension JSONDecoder: TopLevelDecoder { } + +extension PropertyListEncoder: TopLevelEncoder { } +extension PropertyListDecoder: TopLevelDecoder { } +#endif diff --git a/Sources/FirebladeECS/Family1.swift b/Sources/FirebladeECS/Family1.swift index b9b8be7..344e36f 100644 --- a/Sources/FirebladeECS/Family1.swift +++ b/Sources/FirebladeECS/Family1.swift @@ -36,6 +36,18 @@ public struct Requires1: FamilyRequirementsManaging where A: Component { } } +extension Requires1: FamilyDecoding where A: Decodable { + public static func decode(componentsIn container: KeyedDecodingContainer, using strategy: CodingStrategy) throws -> A { + try container.decode(A.self, forKey: strategy.codingKey(for: A.self)) + } +} + +extension Requires1: FamilyEncoding where A: Encodable { + public static func encode(components: Components, into container: inout KeyedEncodingContainer, using strategy: CodingStrategy) throws { + try container.encode(components, forKey: strategy.codingKey(for: A.self)) + } +} + extension Nexus { public func family( requires componentA: A.Type, diff --git a/Sources/FirebladeECS/Family2.swift b/Sources/FirebladeECS/Family2.swift index 8aad060..1f1e58e 100644 --- a/Sources/FirebladeECS/Family2.swift +++ b/Sources/FirebladeECS/Family2.swift @@ -42,6 +42,21 @@ public struct Requires2: FamilyRequirementsManaging where A: Component, B: } } +extension Requires2: FamilyEncoding where A: Encodable, B: Encodable { + public static func encode(components: (A, B), into container: inout KeyedEncodingContainer, using strategy: CodingStrategy) throws { + try container.encode(components.0, forKey: strategy.codingKey(for: A.self)) + try container.encode(components.1, forKey: strategy.codingKey(for: B.self)) + } +} + +extension Requires2: FamilyDecoding where A: Decodable, B: Decodable { + public static func decode(componentsIn container: KeyedDecodingContainer, using strategy: CodingStrategy) throws -> (A, B) { + let compA = try container.decode(A.self, forKey: strategy.codingKey(for: A.self)) + let compB = try container.decode(B.self, forKey: strategy.codingKey(for: B.self)) + return Components(compA, compB) + } +} + extension Nexus { public func family( requiresAll componentA: A.Type, diff --git a/Sources/FirebladeECS/FamilyProtocols.swift b/Sources/FirebladeECS/FamilyProtocols.swift new file mode 100644 index 0000000..ea7f140 --- /dev/null +++ b/Sources/FirebladeECS/FamilyProtocols.swift @@ -0,0 +1,69 @@ +// +// FamilyRequirementsManaging.swift +// +// +// Created by Christian Treffs on 21.08.19. +// + +public protocol FamilyRequirementsManaging { + associatedtype Components + associatedtype ComponentTypes + associatedtype EntityAndComponents + associatedtype RelativesDescending + + init(_ types: ComponentTypes) + + var componentTypes: [Component.Type] { get } + + static func components(nexus: Nexus, entityId: EntityIdentifier) -> Components + static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> EntityAndComponents + static func relativesDescending(nexus: Nexus, parentId: EntityIdentifier, childId: EntityIdentifier) -> RelativesDescending + + static func createMember(nexus: Nexus, components: Components) -> Entity +} + +public protocol FamilyEncoding: FamilyRequirementsManaging { + static func encode(components: [Components], into container: inout UnkeyedEncodingContainer, using strategy: CodingStrategy) throws + static func encode(components: Components, into container: inout KeyedEncodingContainer, using strategy: CodingStrategy) throws +} + +extension FamilyEncoding { + public static func encode(components: [Components], into container: inout UnkeyedEncodingContainer, using strategy: CodingStrategy) throws { + for comps in components { + var container = container.nestedContainer(keyedBy: DynamicCodingKey.self) + try Self.encode(components: comps, into: &container, using: strategy) + } + } +} + +public protocol FamilyDecoding: FamilyRequirementsManaging { + static func decode(componentsIn unkeyedContainer: inout UnkeyedDecodingContainer, using strategy: CodingStrategy) throws -> [Components] + static func decode(componentsIn container: KeyedDecodingContainer, using strategy: CodingStrategy) throws -> Components +} + +extension FamilyDecoding { + public static func decode(componentsIn unkeyedContainer: inout UnkeyedDecodingContainer, using strategy: CodingStrategy) throws -> [Components] { + var components = [Components]() + if let count = unkeyedContainer.count { + components.reserveCapacity(count) + } + while !unkeyedContainer.isAtEnd { + let container = try unkeyedContainer.nestedContainer(keyedBy: DynamicCodingKey.self) + let comps = try Self.decode(componentsIn: container, using: strategy) + components.append(comps) + } + return components + } +} + +public protocol CodingStrategy { + func codingKey(for componentType: C.Type) -> DynamicCodingKey where C: Component +} + +public struct DynamicCodingKey: CodingKey { + public var intValue: Int? + public var stringValue: String + + public init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" } + public init?(stringValue: String) { self.stringValue = stringValue } +} diff --git a/Sources/FirebladeECS/FamilyRequirementsManaging.swift b/Sources/FirebladeECS/FamilyRequirementsManaging.swift deleted file mode 100644 index b3605f4..0000000 --- a/Sources/FirebladeECS/FamilyRequirementsManaging.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// FamilyRequirementsManaging.swift -// -// -// Created by Christian Treffs on 21.08.19. -// - -public protocol FamilyRequirementsManaging { - associatedtype Components - associatedtype ComponentTypes - associatedtype EntityAndComponents - associatedtype RelativesDescending - - init(_ types: ComponentTypes) - - var componentTypes: [Component.Type] { get } - - static func components(nexus: Nexus, entityId: EntityIdentifier) -> Components - static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> EntityAndComponents - static func relativesDescending(nexus: Nexus, parentId: EntityIdentifier, childId: EntityIdentifier) -> RelativesDescending - - static func createMember(nexus: Nexus, components: Components) -> Entity -} diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index a15385e..306e4d4 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -31,6 +31,8 @@ public final class Nexus { /// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family. @usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet] + public final var codingStrategy: CodingStrategy + public final weak var delegate: NexusEventDelegate? public convenience init() { @@ -39,7 +41,8 @@ public final class Nexus { componentsByEntity: [:], entityIdGenerator: EntityIdentifierGenerator(), familyMembersByTraits: [:], - childrenByParentEntity: [:]) + childrenByParentEntity: [:], + codingStrategy: DefaultCodingStrategy()) } internal init(entityStorage: UnorderedSparseSet, @@ -47,13 +50,15 @@ public final class Nexus { componentsByEntity: [EntityIdentifier: Set], entityIdGenerator: EntityIdentifierGenerator, familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet], - childrenByParentEntity: [EntityIdentifier: Set]) { + childrenByParentEntity: [EntityIdentifier: Set], + codingStrategy: CodingStrategy) { self.entityStorage = entityStorage self.componentsByType = componentsByType self.componentIdsByEntity = componentsByEntity self.familyMembersByTraits = familyMembersByTraits self.childrenByParentEntity = childrenByParentEntity self.entityIdGenerator = entityIdGenerator + self.codingStrategy = codingStrategy } deinit { @@ -76,3 +81,12 @@ extension Nexus: CustomDebugStringConvertible { "" } } + +// MARK: - default coding strategy +public struct DefaultCodingStrategy: CodingStrategy { + public init() { } + + public func codingKey(for componentType: C.Type) -> DynamicCodingKey where C: Component { + DynamicCodingKey(stringValue: "\(C.self)")! + } +} diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index ded9b86..31c3237 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -55,6 +55,28 @@ class Index: Component { } } +final class MyComponent: Component { + var name: String + var flag: Bool + + init(name: String, flag: Bool) { + self.name = name + self.flag = flag + } +} +extension MyComponent: Decodable { } +extension MyComponent: Encodable { } + +final class YourComponent: Component { + var number: Float + + init(number: Float) { + self.number = number + } +} +extension YourComponent: Decodable { } +extension YourComponent: Encodable { } + final class SingleGameState: SingleComponent { var shouldQuit: Bool = false var playerHealth: Int = 67 diff --git a/Tests/FirebladeECSTests/FamilyCodingTests.swift b/Tests/FirebladeECSTests/FamilyCodingTests.swift new file mode 100644 index 0000000..4e3c8b3 --- /dev/null +++ b/Tests/FirebladeECSTests/FamilyCodingTests.swift @@ -0,0 +1,102 @@ +// +// FamilyCodingTests.swift +// +// +// Created by Christian Treffs on 22.07.20. +// + +import FirebladeECS +import XCTest + +final class FamilyCodingTests: XCTestCase { + + func testEncodingFamily1() throws { + let nexus = Nexus() + + let family = nexus.family(requires: MyComponent.self) + family.createMember(with: MyComponent(name: "My Name", flag: true)) + family.createMember(with: MyComponent(name: "Your Name", flag: false)) + XCTAssertEqual(family.count, 2) + + var jsonEncoder = JSONEncoder() + let encodedData = try family.encodeMembers(using: &jsonEncoder) + XCTAssertGreaterThanOrEqual(encodedData.count, 90) + } + + func testDecodingFamily1() throws { + let jsonString = """ + [ + { + "MyComponent": { + "name": "My Name", + "flag": true + } + }, + { + "MyComponent": { + "name": "Your Name", + "flag": false + } + } + ] + """ + let jsonData = jsonString.data(using: .utf8)! + + let nexus = Nexus() + let family = nexus.family(requires: MyComponent.self) + XCTAssertTrue(family.isEmpty) + var jsonDecoder = JSONDecoder() + try family.decodeMembers(from: jsonData, using: &jsonDecoder) + + XCTAssertEqual(family.count, 2) + + } + + func testEncodeFamily2() throws { + let nexus = Nexus() + + let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self) + family.createMember(with: (MyComponent(name: "My Name", flag: true), YourComponent(number: 1.23))) + family.createMember(with: (MyComponent(name: "Your Name", flag: false), YourComponent(number: 3.45))) + XCTAssertEqual(family.count, 2) + + var jsonEncoder = JSONEncoder() + let encodedData = try family.encodeMembers(using: &jsonEncoder) + XCTAssertGreaterThanOrEqual(encodedData.count, 190) + } + + func testDecodingFamily2() throws { + let jsonString = """ + [ + { + "MyComponent": { + "name": "My Name", + "flag": true + }, + "YourComponent": { + "number": 2.13 + } + }, + { + "MyComponent": { + "name": "Your Name", + "flag": false + }, + "YourComponent": { + "number": 3.1415 + } + } + ] + """ + + let jsonData = jsonString.data(using: .utf8)! + + let nexus = Nexus() + + let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self) + XCTAssertTrue(family.isEmpty) + var jsonDecoder = JSONDecoder() + try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(family.count, 2) + } +} diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index a1100e4..46ae684 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -34,6 +34,18 @@ extension EntityTests { ] } +extension FamilyCodingTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__FamilyCodingTests = [ + ("testDecodingFamily1", testDecodingFamily1), + ("testDecodingFamily2", testDecodingFamily2), + ("testEncodeFamily2", testEncodeFamily2), + ("testEncodingFamily1", testEncodingFamily1) + ] +} + extension FamilyTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -144,6 +156,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests), testCase(ComponentTests.__allTests__ComponentTests), testCase(EntityTests.__allTests__EntityTests), + testCase(FamilyCodingTests.__allTests__FamilyCodingTests), testCase(FamilyTests.__allTests__FamilyTests), testCase(FamilyTraitsTests.__allTests__FamilyTraitsTests), testCase(HashingTests.__allTests__HashingTests), From 6cc1ba3c689d1344f1b1fe3176732a7478d29bff Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 22 Jul 2020 13:47:11 +0200 Subject: [PATCH 4/9] Fix data size comparison --- Tests/FirebladeECSTests/FamilyCodingTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/FirebladeECSTests/FamilyCodingTests.swift b/Tests/FirebladeECSTests/FamilyCodingTests.swift index 4e3c8b3..4669bcf 100644 --- a/Tests/FirebladeECSTests/FamilyCodingTests.swift +++ b/Tests/FirebladeECSTests/FamilyCodingTests.swift @@ -62,7 +62,7 @@ final class FamilyCodingTests: XCTestCase { var jsonEncoder = JSONEncoder() let encodedData = try family.encodeMembers(using: &jsonEncoder) - XCTAssertGreaterThanOrEqual(encodedData.count, 190) + XCTAssertGreaterThanOrEqual(encodedData.count, 91) } func testDecodingFamily2() throws { From da94c055f8e623dfafb8f58bf49a1a062465fb21 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 22 Jul 2020 14:09:29 +0200 Subject: [PATCH 5/9] Add coding to family 3 --- Sources/FirebladeECS/Family3.swift | 17 ++++++ Tests/FirebladeECSTests/Base.swift | 4 +- .../FirebladeECSTests/FamilyCodingTests.swift | 56 +++++++++++++++++++ Tests/FirebladeECSTests/XCTestManifests.swift | 2 + 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/Sources/FirebladeECS/Family3.swift b/Sources/FirebladeECS/Family3.swift index 0bfde27..a244a58 100644 --- a/Sources/FirebladeECS/Family3.swift +++ b/Sources/FirebladeECS/Family3.swift @@ -46,6 +46,23 @@ public struct Requires3: FamilyRequirementsManaging where A: Component, } } +extension Requires3: FamilyEncoding where A: Encodable, B: Encodable, C: Encodable { + public static func encode(components: (A, B, C), into container: inout KeyedEncodingContainer, using strategy: CodingStrategy) throws { + try container.encode(components.0, forKey: strategy.codingKey(for: A.self)) + try container.encode(components.1, forKey: strategy.codingKey(for: B.self)) + try container.encode(components.2, forKey: strategy.codingKey(for: C.self)) + } +} + +extension Requires3: FamilyDecoding where A: Decodable, B: Decodable, C: Decodable { + public static func decode(componentsIn container: KeyedDecodingContainer, using strategy: CodingStrategy) throws -> (A, B, C) { + let compA = try container.decode(A.self, forKey: strategy.codingKey(for: A.self)) + let compB = try container.decode(B.self, forKey: strategy.codingKey(for: B.self)) + let compC = try container.decode(C.self, forKey: strategy.codingKey(for: C.self)) + return Components(compA, compB, compC) + } +} + extension Nexus { public func family( requiresAll componentA: A.Type, diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index 31c3237..0f14bf3 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -18,14 +18,16 @@ class Name: Component { } } -class Position: Component { +final class Position: Component { var x: Int var y: Int + init(x: Int, y: Int) { self.x = x self.y = y } } +extension Position: Codable { } class Velocity: Component { var a: Float diff --git a/Tests/FirebladeECSTests/FamilyCodingTests.swift b/Tests/FirebladeECSTests/FamilyCodingTests.swift index 4669bcf..f028613 100644 --- a/Tests/FirebladeECSTests/FamilyCodingTests.swift +++ b/Tests/FirebladeECSTests/FamilyCodingTests.swift @@ -99,4 +99,60 @@ final class FamilyCodingTests: XCTestCase { try family.decodeMembers(from: jsonData, using: &jsonDecoder) XCTAssertEqual(family.count, 2) } + + func testEncodeFamily3() throws { + let nexus = Nexus() + + let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self, Position.self) + family.createMember(with: (MyComponent(name: "My Name", flag: true), YourComponent(number: 1.23), Position(x: 1, y: 2))) + family.createMember(with: (MyComponent(name: "Your Name", flag: false), YourComponent(number: 3.45), Position(x: 3, y: 4))) + XCTAssertEqual(family.count, 2) + + var jsonEncoder = JSONEncoder() + let encodedData = try family.encodeMembers(using: &jsonEncoder) + XCTAssertGreaterThanOrEqual(encodedData.count, 200) + } + + func testDecodingFamily3() throws { + let jsonString = """ + [ + { + "MyComponent": { + "name": "My Name", + "flag": true + }, + "YourComponent": { + "number": 1.23 + }, + "Position": { + "x": 1, + "y": 2 + } + }, + { + "MyComponent": { + "name": "Your Name", + "flag": false + }, + "YourComponent": { + "number": 3.45 + }, + "Position": { + "x": 3, + "y": 4 + } + } + ] + """ + + let jsonData = jsonString.data(using: .utf8)! + + let nexus = Nexus() + + let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self, Position.self) + XCTAssertTrue(family.isEmpty) + var jsonDecoder = JSONDecoder() + try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(family.count, 2) + } } diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 46ae684..2084de9 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -41,7 +41,9 @@ extension FamilyCodingTests { static let __allTests__FamilyCodingTests = [ ("testDecodingFamily1", testDecodingFamily1), ("testDecodingFamily2", testDecodingFamily2), + ("testDecodingFamily3", testDecodingFamily3), ("testEncodeFamily2", testEncodeFamily2), + ("testEncodeFamily3", testEncodeFamily3), ("testEncodingFamily1", testEncodingFamily1) ] } From 6c5a1d29f79934be1e7673cbcbc23f88b6d3b31b Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 22 Jul 2020 14:20:07 +0200 Subject: [PATCH 6/9] Add coding to family 4 --- Sources/FirebladeECS/Family4.swift | 19 ++++++ Tests/FirebladeECSTests/Base.swift | 15 +++-- .../FirebladeECSTests/FamilyCodingTests.swift | 66 +++++++++++++++++++ Tests/FirebladeECSTests/XCTestManifests.swift | 2 + 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/Sources/FirebladeECS/Family4.swift b/Sources/FirebladeECS/Family4.swift index 17a0189..ec9e40d 100644 --- a/Sources/FirebladeECS/Family4.swift +++ b/Sources/FirebladeECS/Family4.swift @@ -51,6 +51,25 @@ public struct Requires4: FamilyRequirementsManaging where A: Compone } } +extension Requires4: FamilyEncoding where A: Encodable, B: Encodable, C: Encodable, D: Encodable { + public static func encode(components: (A, B, C, D), into container: inout KeyedEncodingContainer, using strategy: CodingStrategy) throws { + try container.encode(components.0, forKey: strategy.codingKey(for: A.self)) + try container.encode(components.1, forKey: strategy.codingKey(for: B.self)) + try container.encode(components.2, forKey: strategy.codingKey(for: C.self)) + try container.encode(components.3, forKey: strategy.codingKey(for: D.self)) + } +} + +extension Requires4: FamilyDecoding where A: Decodable, B: Decodable, C: Decodable, D: Decodable { + public static func decode(componentsIn container: KeyedDecodingContainer, using strategy: CodingStrategy) throws -> (A, B, C, D) { + let compA = try container.decode(A.self, forKey: strategy.codingKey(for: A.self)) + let compB = try container.decode(B.self, forKey: strategy.codingKey(for: B.self)) + let compC = try container.decode(C.self, forKey: strategy.codingKey(for: C.self)) + let compD = try container.decode(D.self, forKey: strategy.codingKey(for: D.self)) + return Components(compA, compB, compC, compD) + } +} + extension Nexus { public func family( requiresAll componentA: A.Type, diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index 0f14bf3..97143c9 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -43,11 +43,18 @@ class Party: Component { } } -class Color: Component { - var r: UInt8 = 0 - var g: UInt8 = 0 - var b: UInt8 = 0 +final class Color: Component { + var r: UInt8 + var g: UInt8 + var b: UInt8 + + init(r: UInt8 = 0, g: UInt8 = 0, b: UInt8 = 0) { + self.r = r + self.g = g + self.b = b + } } +extension Color: Codable { } class Index: Component { var index: Int diff --git a/Tests/FirebladeECSTests/FamilyCodingTests.swift b/Tests/FirebladeECSTests/FamilyCodingTests.swift index f028613..4fa1e2f 100644 --- a/Tests/FirebladeECSTests/FamilyCodingTests.swift +++ b/Tests/FirebladeECSTests/FamilyCodingTests.swift @@ -155,4 +155,70 @@ final class FamilyCodingTests: XCTestCase { try family.decodeMembers(from: jsonData, using: &jsonDecoder) XCTAssertEqual(family.count, 2) } + + func testEncodeFamily4() throws { + let nexus = Nexus() + + let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self, Position.self, Color.self) + family.createMember(with: (MyComponent(name: "My Name", flag: true), YourComponent(number: 1.23), Position(x: 1, y: 2), Color(r: 1, g: 2, b: 3))) + family.createMember(with: (MyComponent(name: "Your Name", flag: false), YourComponent(number: 3.45), Position(x: 3, y: 4), Color(r: 4, g: 5, b: 6))) + XCTAssertEqual(family.count, 2) + + var jsonEncoder = JSONEncoder() + let encodedData = try family.encodeMembers(using: &jsonEncoder) + XCTAssertGreaterThanOrEqual(encodedData.count, 250) + } + + func testDecodeFamily4() throws { + let jsonString = """ + [ + { + "Color": { + "r": 1, + "g": 2, + "b": 3 + }, + "Position": { + "x": 1, + "y": 2 + }, + "MyComponent": { + "name": "My Name", + "flag": true + }, + "YourComponent": { + "number": 1.2300000190734863 + } + }, + { + "Color": { + "r": 4, + "g": 5, + "b": 6 + }, + "Position": { + "x": 3, + "y": 4 + }, + "MyComponent": { + "name": "Your Name", + "flag": false + }, + "YourComponent": { + "number": 3.4500000476837158 + } + } + ] + """ + + let jsonData = jsonString.data(using: .utf8)! + + let nexus = Nexus() + + let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self, Position.self, Color.self) + XCTAssertTrue(family.isEmpty) + var jsonDecoder = JSONDecoder() + try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(family.count, 2) + } } diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 2084de9..282e652 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -39,11 +39,13 @@ extension FamilyCodingTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__FamilyCodingTests = [ + ("testDecodeFamily4", testDecodeFamily4), ("testDecodingFamily1", testDecodingFamily1), ("testDecodingFamily2", testDecodingFamily2), ("testDecodingFamily3", testDecodingFamily3), ("testEncodeFamily2", testEncodeFamily2), ("testEncodeFamily3", testEncodeFamily3), + ("testEncodeFamily4", testEncodeFamily4), ("testEncodingFamily1", testEncodingFamily1) ] } From 99710fd5e1a26eacec06cd478f089ded3897bb60 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 22 Jul 2020 14:32:36 +0200 Subject: [PATCH 7/9] Add coding to family 5 --- Sources/FirebladeECS/Family5.swift | 21 +++ Tests/FirebladeECSTests/Base.swift | 4 +- .../FirebladeECSTests/FamilyCodingTests.swift | 121 ++++++++++++++++++ Tests/FirebladeECSTests/XCTestManifests.swift | 5 +- 4 files changed, 149 insertions(+), 2 deletions(-) diff --git a/Sources/FirebladeECS/Family5.swift b/Sources/FirebladeECS/Family5.swift index a4e8422..6bbf9e2 100644 --- a/Sources/FirebladeECS/Family5.swift +++ b/Sources/FirebladeECS/Family5.swift @@ -56,6 +56,27 @@ public struct Requires5: FamilyRequirementsManaging where A: Comp } } +extension Requires5: FamilyEncoding where A: Encodable, B: Encodable, C: Encodable, D: Encodable, E: Encodable { + public static func encode(components: (A, B, C, D, E), into container: inout KeyedEncodingContainer, using strategy: CodingStrategy) throws { + try container.encode(components.0, forKey: strategy.codingKey(for: A.self)) + try container.encode(components.1, forKey: strategy.codingKey(for: B.self)) + try container.encode(components.2, forKey: strategy.codingKey(for: C.self)) + try container.encode(components.3, forKey: strategy.codingKey(for: D.self)) + try container.encode(components.4, forKey: strategy.codingKey(for: E.self)) + } +} + +extension Requires5: FamilyDecoding where A: Decodable, B: Decodable, C: Decodable, D: Decodable, E: Decodable { + public static func decode(componentsIn container: KeyedDecodingContainer, using strategy: CodingStrategy) throws -> (A, B, C, D, E) { + let compA = try container.decode(A.self, forKey: strategy.codingKey(for: A.self)) + let compB = try container.decode(B.self, forKey: strategy.codingKey(for: B.self)) + let compC = try container.decode(C.self, forKey: strategy.codingKey(for: C.self)) + let compD = try container.decode(D.self, forKey: strategy.codingKey(for: D.self)) + let compE = try container.decode(E.self, forKey: strategy.codingKey(for: E.self)) + return Components(compA, compB, compC, compD, compE) + } +} + extension Nexus { // swiftlint:disable function_parameter_count public func family( diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index 97143c9..12d723e 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -36,12 +36,14 @@ class Velocity: Component { } } -class Party: Component { +final class Party: Component { var partying: Bool + init(partying: Bool) { self.partying = partying } } +extension Party: Codable { } final class Color: Component { var r: UInt8 diff --git a/Tests/FirebladeECSTests/FamilyCodingTests.swift b/Tests/FirebladeECSTests/FamilyCodingTests.swift index 4fa1e2f..0de9117 100644 --- a/Tests/FirebladeECSTests/FamilyCodingTests.swift +++ b/Tests/FirebladeECSTests/FamilyCodingTests.swift @@ -221,4 +221,125 @@ final class FamilyCodingTests: XCTestCase { try family.decodeMembers(from: jsonData, using: &jsonDecoder) XCTAssertEqual(family.count, 2) } + + func testEncodeFamily5() throws { + let nexus = Nexus() + + let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self, Position.self, Color.self, Party.self) + family.createMember(with: (MyComponent(name: "My Name", flag: true), YourComponent(number: 1.23), Position(x: 1, y: 2), Color(r: 1, g: 2, b: 3), Party(partying: true))) + family.createMember(with: (MyComponent(name: "Your Name", flag: false), YourComponent(number: 3.45), Position(x: 3, y: 4), Color(r: 4, g: 5, b: 6), Party(partying: false))) + XCTAssertEqual(family.count, 2) + + var jsonEncoder = JSONEncoder() + let encodedData = try family.encodeMembers(using: &jsonEncoder) + XCTAssertGreaterThanOrEqual(encodedData.count, 320) + } + + func testDecodeFamily5() throws { + let jsonString = """ + [ + { + "Color": { + "r": 1, + "g": 2, + "b": 3 + }, + "Position": { + "x": 1, + "y": 2 + }, + "MyComponent": { + "name": "My Name", + "flag": true + }, + "YourComponent": { + "number": 1.23 + }, + "Party": { + "partying": true + } + }, + { + "Color": { + "r": 4, + "g": 5, + "b": 6 + }, + "Position": { + "x": 3, + "y": 4 + }, + "MyComponent": { + "name": "Your Name", + "flag": false + }, + "YourComponent": { + "number": 3.45 + }, + "Party": { + "partying": false + } + } + ] + """ + + let jsonData = jsonString.data(using: .utf8)! + + let nexus = Nexus() + + let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self, Position.self, Color.self, Party.self) + XCTAssertTrue(family.isEmpty) + var jsonDecoder = JSONDecoder() + try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(family.count, 2) + } + + func testFailDecodingFamily() { + + let jsonString = """ + [ + { + "Color": { + "r": 1, + "g": 2, + "b": 3 + }, + "Position": { + "x": 1, + "y": 2 + }, + "YourComponent": { + "number": 1.23 + }, + "Party": { + "partying": true + } + }, + { + "Color": { + "r": 4, + "g": 5, + "b": 6 + }, + "Position": { + "x": 3, + "y": 4 + }, + "YourComponent": { + "number": 3.45 + }, + "Party": { + "partying": false + } + } + ] + """ + let jsonData = jsonString.data(using: .utf8)! + var jsonDecoder = JSONDecoder() + + let nexus = Nexus() + + let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self) + XCTAssertThrowsError(try family.decodeMembers(from: jsonData, using: &jsonDecoder)) + } } diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 282e652..2a2b454 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -40,13 +40,16 @@ extension FamilyCodingTests { // to regenerate. static let __allTests__FamilyCodingTests = [ ("testDecodeFamily4", testDecodeFamily4), + ("testDecodeFamily5", testDecodeFamily5), ("testDecodingFamily1", testDecodingFamily1), ("testDecodingFamily2", testDecodingFamily2), ("testDecodingFamily3", testDecodingFamily3), ("testEncodeFamily2", testEncodeFamily2), ("testEncodeFamily3", testEncodeFamily3), ("testEncodeFamily4", testEncodeFamily4), - ("testEncodingFamily1", testEncodingFamily1) + ("testEncodeFamily5", testEncodeFamily5), + ("testEncodingFamily1", testEncodingFamily1), + ("testFailDecodingFamily", testFailDecodingFamily) ] } From 416b6dab367b9ea24b203f513fb4ee6d5c563f9e Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 22 Jul 2020 14:48:19 +0200 Subject: [PATCH 8/9] Cleanups --- ...mily+Codable.swift => Family+Coding.swift} | 39 ++++++------------- .../FirebladeECS/Foundation+Extensions.swift | 20 ++++++++++ 2 files changed, 32 insertions(+), 27 deletions(-) rename Sources/FirebladeECS/{Family+Codable.swift => Family+Coding.swift} (89%) create mode 100644 Sources/FirebladeECS/Foundation+Extensions.swift diff --git a/Sources/FirebladeECS/Family+Codable.swift b/Sources/FirebladeECS/Family+Coding.swift similarity index 89% rename from Sources/FirebladeECS/Family+Codable.swift rename to Sources/FirebladeECS/Family+Coding.swift index 050c2c0..2cb764f 100644 --- a/Sources/FirebladeECS/Family+Codable.swift +++ b/Sources/FirebladeECS/Family+Coding.swift @@ -1,5 +1,5 @@ // -// Family+Codable.swift +// Family+Coding.swift // // // Created by Christian Treffs on 22.07.20. @@ -9,14 +9,11 @@ private struct FamilyMemberContainer where R: FamilyRequirementsManaging { let components: [R.Components] } -extension FamilyMemberContainer: Decodable where R: FamilyDecoding { - init(from decoder: Decoder) throws { - var familyContainer = try decoder.unkeyedContainer() - let strategy = decoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy() - self.components = try R.decode(componentsIn: &familyContainer, using: strategy) - } +extension CodingUserInfoKey { + fileprivate static let nexusCodingStrategy = CodingUserInfoKey(rawValue: "nexusCodingStrategy")! } +// MARK: - encoding extension FamilyMemberContainer: Encodable where R: FamilyEncoding { func encode(to encoder: Encoder) throws { let strategy = encoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy() @@ -25,7 +22,6 @@ extension FamilyMemberContainer: Encodable where R: FamilyEncoding { } } -// MARK: - encoding public protocol TopLevelEncoder { /// The type this encoder produces. associatedtype Output @@ -54,6 +50,14 @@ extension Family where R: FamilyEncoding { } // MARK: - decoding +extension FamilyMemberContainer: Decodable where R: FamilyDecoding { + init(from decoder: Decoder) throws { + var familyContainer = try decoder.unkeyedContainer() + let strategy = decoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy() + self.components = try R.decode(componentsIn: &familyContainer, using: strategy) + } +} + public protocol TopLevelDecoder { /// The type this decoder accepts. associatedtype Input @@ -80,22 +84,3 @@ extension Family where R: FamilyDecoding { } } } - -extension CodingUserInfoKey { - fileprivate static let nexusCodingStrategy = CodingUserInfoKey(rawValue: "nexusCodingStrategy")! -} - -// MARK: - foundation -#if canImport(Foundation) -import class Foundation.JSONEncoder -import class Foundation.JSONDecoder - -import class Foundation.PropertyListEncoder -import class Foundation.PropertyListDecoder - -extension JSONEncoder: TopLevelEncoder { } -extension JSONDecoder: TopLevelDecoder { } - -extension PropertyListEncoder: TopLevelEncoder { } -extension PropertyListDecoder: TopLevelDecoder { } -#endif diff --git a/Sources/FirebladeECS/Foundation+Extensions.swift b/Sources/FirebladeECS/Foundation+Extensions.swift new file mode 100644 index 0000000..f73077c --- /dev/null +++ b/Sources/FirebladeECS/Foundation+Extensions.swift @@ -0,0 +1,20 @@ +// +// Foundation+Extensions.swift +// +// +// Created by Christian Treffs on 22.07.20. +// + +#if canImport(Foundation) +import class Foundation.JSONEncoder +import class Foundation.JSONDecoder + +import class Foundation.PropertyListEncoder +import class Foundation.PropertyListDecoder + +extension JSONEncoder: TopLevelEncoder { } +extension JSONDecoder: TopLevelDecoder { } + +extension PropertyListEncoder: TopLevelEncoder { } +extension PropertyListDecoder: TopLevelDecoder { } +#endif From 70a210b27615f52eccfd4b80fe02f7d656bedeb0 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 23 Jul 2020 22:04:17 +0200 Subject: [PATCH 9/9] Return newly created entities --- Sources/FirebladeECS/Family+Coding.swift | 9 +++++---- .../FirebladeECSTests/FamilyCodingTests.swift | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Sources/FirebladeECS/Family+Coding.swift b/Sources/FirebladeECS/Family+Coding.swift index 2cb764f..692758f 100644 --- a/Sources/FirebladeECS/Family+Coding.swift +++ b/Sources/FirebladeECS/Family+Coding.swift @@ -76,11 +76,12 @@ extension Family where R: FamilyDecoding { /// - Parameters: /// - data: The data decoded by decoder. A unkeyed container of family members (keyed component containers) is expected. /// - decoder: The decoder to use for decoding family member data. Decoder respects the coding strategy set at `nexus.codingStrategy`. - public func decodeMembers(from data: Decoder.Input, using decoder: inout Decoder) throws where Decoder: TopLevelDecoder { + /// - Returns: returns the newly added entities. + @discardableResult + public func decodeMembers(from data: Decoder.Input, using decoder: inout Decoder) throws -> [Entity] where Decoder: TopLevelDecoder { decoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy let familyMembers = try decoder.decode(FamilyMemberContainer.self, from: data) - for components in familyMembers.components { - createMember(with: components) - } + return familyMembers.components + .map { createMember(with: $0) } } } diff --git a/Tests/FirebladeECSTests/FamilyCodingTests.swift b/Tests/FirebladeECSTests/FamilyCodingTests.swift index 0de9117..8da696f 100644 --- a/Tests/FirebladeECSTests/FamilyCodingTests.swift +++ b/Tests/FirebladeECSTests/FamilyCodingTests.swift @@ -46,8 +46,8 @@ final class FamilyCodingTests: XCTestCase { let family = nexus.family(requires: MyComponent.self) XCTAssertTrue(family.isEmpty) var jsonDecoder = JSONDecoder() - try family.decodeMembers(from: jsonData, using: &jsonDecoder) - + let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(newEntities.count, 2) XCTAssertEqual(family.count, 2) } @@ -96,7 +96,8 @@ final class FamilyCodingTests: XCTestCase { let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self) XCTAssertTrue(family.isEmpty) var jsonDecoder = JSONDecoder() - try family.decodeMembers(from: jsonData, using: &jsonDecoder) + let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(newEntities.count, 2) XCTAssertEqual(family.count, 2) } @@ -150,10 +151,13 @@ final class FamilyCodingTests: XCTestCase { let nexus = Nexus() let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self, Position.self) + let family2 = nexus.family(requiresAll: YourComponent.self, MyComponent.self, excludesAll: Index.self) XCTAssertTrue(family.isEmpty) var jsonDecoder = JSONDecoder() - try family.decodeMembers(from: jsonData, using: &jsonDecoder) + let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(newEntities.count, 2) XCTAssertEqual(family.count, 2) + XCTAssertEqual(family2.count, 2) } func testEncodeFamily4() throws { @@ -218,7 +222,8 @@ final class FamilyCodingTests: XCTestCase { let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self, Position.self, Color.self) XCTAssertTrue(family.isEmpty) var jsonDecoder = JSONDecoder() - try family.decodeMembers(from: jsonData, using: &jsonDecoder) + let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(newEntities.count, 2) XCTAssertEqual(family.count, 2) } @@ -290,7 +295,8 @@ final class FamilyCodingTests: XCTestCase { let family = nexus.family(requiresAll: YourComponent.self, MyComponent.self, Position.self, Color.self, Party.self) XCTAssertTrue(family.isEmpty) var jsonDecoder = JSONDecoder() - try family.decodeMembers(from: jsonData, using: &jsonDecoder) + let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder) + XCTAssertEqual(newEntities.count, 2) XCTAssertEqual(family.count, 2) }