WIP: decoding + encoding
This commit is contained in:
parent
4ed52db56c
commit
6812e53d7a
|
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// Family+Codable.swift
|
||||
//
|
||||
//
|
||||
// Created by Christian Treffs on 22.07.20.
|
||||
//
|
||||
|
||||
private struct FamilyMemberContainer<R> 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<T>(_ 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<Encoder>(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<R>(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<T>(_ 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<Decoder>(from data: Decoder.Input, using decoder: inout Decoder) throws where Decoder: TopLevelDecoder {
|
||||
decoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy
|
||||
let familyMembers = try decoder.decode(FamilyMemberContainer<R>.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
|
||||
|
|
@ -36,6 +36,18 @@ public struct Requires1<A>: FamilyRequirementsManaging where A: Component {
|
|||
}
|
||||
}
|
||||
|
||||
extension Requires1: FamilyDecoding where A: Decodable {
|
||||
public static func decode(componentsIn container: KeyedDecodingContainer<DynamicCodingKey>, 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<DynamicCodingKey>, using strategy: CodingStrategy) throws {
|
||||
try container.encode(components, forKey: strategy.codingKey(for: A.self))
|
||||
}
|
||||
}
|
||||
|
||||
extension Nexus {
|
||||
public func family<A>(
|
||||
requires componentA: A.Type,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,21 @@ public struct Requires2<A, B>: FamilyRequirementsManaging where A: Component, B:
|
|||
}
|
||||
}
|
||||
|
||||
extension Requires2: FamilyEncoding where A: Encodable, B: Encodable {
|
||||
public static func encode(components: (A, B), into container: inout KeyedEncodingContainer<DynamicCodingKey>, 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<DynamicCodingKey>, 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<A, B>(
|
||||
requiresAll componentA: A.Type,
|
||||
|
|
|
|||
|
|
@ -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<DynamicCodingKey>, 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<DynamicCodingKey>, 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<C>(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 }
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<EntityIdentifier, EntityIdentifier.Id>]
|
||||
|
||||
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<EntityIdentifier, EntityIdentifier.Id>,
|
||||
|
|
@ -47,13 +50,15 @@ public final class Nexus {
|
|||
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
||||
entityIdGenerator: EntityIdentifierGenerator,
|
||||
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>],
|
||||
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>]) {
|
||||
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>],
|
||||
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 {
|
|||
"<Nexus entities:\(numEntities) components:\(numComponents) families:\(numFamilies)>"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - default coding strategy
|
||||
public struct DefaultCodingStrategy: CodingStrategy {
|
||||
public init() { }
|
||||
|
||||
public func codingKey<C>(for componentType: C.Type) -> DynamicCodingKey where C: Component {
|
||||
DynamicCodingKey(stringValue: "\(C.self)")!
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in New Issue