Merge branch 'release/0.13.0'

This commit is contained in:
Christian Treffs 2020-07-23 22:48:12 +02:00
commit cefc97fcbb
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
16 changed files with 778 additions and 30 deletions

View File

@ -34,7 +34,7 @@ import PackageDescription
let package = Package(
name: "YourPackageName",
dependencies: [
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.12.2")
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.13.0")
],
targets: [
.target(
@ -216,6 +216,28 @@ nexus.family(requires: Position.self)
}
```
### 🔗 Serialization
To serialize/deserialize entities you must conform their assigned components to the `Codable` protocol.
Conforming components can then be serialized per family like this:
```swift
// MyComponent and YourComponent both conform to Component and Codable protocols.
let nexus = Nexus()
let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self)
// JSON encode entities from given family.
var jsonEncoder = JSONEncoder()
let encodedData = try family.encodeMembers(using: &jsonEncoder)
// Decode entities into given family from JSON.
// The decoded entities will be added to the nexus.
var jsonDecoder = JSONDecoder()
let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder)
```
## 🧪 Demo
See the [Fireblade ECS Demo App](https://github.com/fireblade-engine/ecs-demo) to get started.

View File

@ -0,0 +1,87 @@
//
// Family+Coding.swift
//
//
// Created by Christian Treffs on 22.07.20.
//
private struct FamilyMemberContainer<R> where R: FamilyRequirementsManaging {
let components: [R.Components]
}
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()
var familyContainer = encoder.unkeyedContainer()
try R.encode(components: components, into: &familyContainer, using: strategy)
}
}
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
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
/// 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`.
/// - Returns: returns the newly added entities.
@discardableResult
public func decodeMembers<Decoder>(from data: Decoder.Input, using decoder: inout Decoder) throws -> [Entity] where Decoder: TopLevelDecoder {
decoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy
let familyMembers = try decoder.decode(FamilyMemberContainer<R>.self, from: data)
return familyMembers.components
.map { createMember(with: $0) }
}
}

View File

@ -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)
}
}

View File

@ -30,6 +30,22 @@ public struct Requires1<A>: 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 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 {

View File

@ -15,6 +15,7 @@ public struct Requires2<A, B>: 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,25 @@ public struct Requires2<A, B>: 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 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 {

View File

@ -40,6 +40,27 @@ public struct Requires3<A, B, C>: 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 Requires3: FamilyEncoding where A: Encodable, B: Encodable, C: Encodable {
public static func encode(components: (A, B, C), 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))
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<DynamicCodingKey>, 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 {

View File

@ -45,6 +45,29 @@ public struct Requires4<A, B, C, D>: 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 Requires4: FamilyEncoding where A: Encodable, B: Encodable, C: Encodable, D: Encodable {
public static func encode(components: (A, B, C, D), 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))
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<DynamicCodingKey>, 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 {

View File

@ -50,6 +50,31 @@ public struct Requires5<A, B, C, D, E>: 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 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<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))
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<DynamicCodingKey>, 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 {

View File

@ -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 }
}

View File

@ -1,21 +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
}

View File

@ -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

View File

@ -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)")!
}
}

View File

@ -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
@ -34,18 +36,27 @@ class Velocity: Component {
}
}
class Party: Component {
final class Party: Component {
var partying: Bool
init(partying: Bool) {
self.partying = partying
}
}
extension Party: Codable { }
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
@ -55,6 +66,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

View File

@ -0,0 +1,351 @@
//
// 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()
let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder)
XCTAssertEqual(newEntities.count, 2)
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, 91)
}
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()
let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder)
XCTAssertEqual(newEntities.count, 2)
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)
let family2 = nexus.family(requiresAll: YourComponent.self, MyComponent.self, excludesAll: Index.self)
XCTAssertTrue(family.isEmpty)
var jsonDecoder = 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 {
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()
let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder)
XCTAssertEqual(newEntities.count, 2)
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()
let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder)
XCTAssertEqual(newEntities.count, 2)
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))
}
}

View File

@ -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)
}
}

View File

@ -34,6 +34,25 @@ extension EntityTests {
]
}
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`
@ -41,6 +60,7 @@ extension FamilyTests {
static let __allTests__FamilyTests = [
("testFamilyAbandoned", testFamilyAbandoned),
("testFamilyBulkDestroy", testFamilyBulkDestroy),
("testFamilyCreateMembers", testFamilyCreateMembers),
("testFamilyCreation", testFamilyCreation),
("testFamilyExchange", testFamilyExchange),
("testFamilyLateMember", testFamilyLateMember),
@ -143,6 +163,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),