Skip to content

Commit

Permalink
Merge branch 'release/0.17.3' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
ctreffs committed Oct 20, 2020
2 parents 450e494 + 09556e3 commit 78f690c
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,6 @@ jobs:
- name: Swift version
run: swift --version
- name: Build
uses: swiftwasm/swiftwasm-action@master
uses: swiftwasm/swiftwasm-action@main
with:
shell-action: swift build --triple wasm32-unknown-wasi
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import PackageDescription
let package = Package(
name: "YourPackageName",
dependencies: [
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.17.2")
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.17.3")
],
targets: [
.target(
Expand Down
51 changes: 51 additions & 0 deletions Sources/FirebladeECS/Entity+Component.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,35 @@ extension Entity {
}

/// Get the value of a component using the key Path to the property in the component.
///
/// A `Comp` instance must be assigned to this entity!
/// - Parameter componentKeyPath: The `KeyPath` to the property of the given component.
@inlinable
public func get<Comp, Value>(valueAt componentKeyPath: KeyPath<Comp, Value>) -> Value where Comp: Component {
self.get(component: Comp.self)![keyPath: componentKeyPath]
}

/// Get the value of a component using the key Path to the property in the component.
///
/// A `Comp` instance must be assigned to this entity!
/// - Parameter componentKeyPath: The `KeyPath` to the property of the given component.
@inlinable
public func get<Comp, Value>(valueAt componentKeyPath: KeyPath<Comp, Value?>) -> Value? where Comp: Component {
self.get(component: Comp.self)![keyPath: componentKeyPath]
}

/// Get the value of a component using the key Path to the property in the component.
@inlinable
public subscript<Comp, Value>(_ componentKeyPath: KeyPath<Comp, Value>) -> Value where Comp: Component {
self.get(valueAt: componentKeyPath)
}

/// Get the value of a component using the key Path to the property in the component.
@inlinable
public subscript<Comp, Value>(_ componentKeyPath: KeyPath<Comp, Value?>) -> Value? where Comp: Component {
self.get(valueAt: componentKeyPath)
}

/// Set the value of a component using the key path to the property in the component.
///
/// **Behavior:**
Expand All @@ -91,6 +108,29 @@ extension Entity {
return true
}

/// Set the value of a component using the key path to the property in the component.
///
/// **Behavior:**
/// - If `Comp` is a component type that is currently *not* assigned to this entity,
/// a new instance of `Comp` will be default initialized and `newValue` will be set at the given keyPath.
///
/// - Parameters:
/// - newValue: The value to set.
/// - componentKeyPath: The `ReferenceWritableKeyPath` to the property of the given component.
/// - Returns: Returns true if an action was performed, false otherwise.
@inlinable
@discardableResult
public func set<Comp, Value>(value newValue: Value?, for componentKeyPath: ReferenceWritableKeyPath<Comp, Value?>) -> Bool where Comp: Component & DefaultInitializable {
guard has(Comp.self) else {
let newInstance = Comp()
newInstance[keyPath: componentKeyPath] = newValue
return nexus.assign(component: newInstance, entityId: identifier)
}

get(component: Comp.self)![keyPath: componentKeyPath] = newValue
return true
}

/// Set the value of a component using the key path to the property in the component.
///
/// **Behavior:**
Expand All @@ -101,4 +141,15 @@ extension Entity {
get { self.get(valueAt: componentKeyPath) }
nonmutating set { self.set(value: newValue, for: componentKeyPath) }
}

/// Set the value of a component using the key path to the property in the component.
///
/// **Behavior:**
/// - If `Comp` is a component type that is currently *not* assigned to this entity,
/// a new instance of `Comp` will be default initialized and `newValue` will be set at the given keyPath.
@inlinable
public subscript<Comp, Value>(_ componentKeyPath: ReferenceWritableKeyPath<Comp, Value?>) -> Value? where Comp: Component & DefaultInitializable {
get { self.get(valueAt: componentKeyPath) }
nonmutating set { self.set(value: newValue, for: componentKeyPath) }
}
}
45 changes: 28 additions & 17 deletions Sources/FirebladeECS/Entity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ public struct Entity {
nexus.count(components: identifier)
}

@discardableResult
public func createEntity() -> Entity {
nexus.createEntity()
}

@discardableResult
public func createEntity(with components: Component...) -> Entity {
createEntity(with: components)
}

@discardableResult
public func createEntity<C>(with components: C) -> Entity where C: Collection, C.Element == Component {
nexus.createEntity(with: components)
}

/// Checks if a component with given type is assigned to this entity.
/// - Parameter type: the component type.
public func has<C>(_ type: C.Type) -> Bool where C: Component {
Expand All @@ -48,9 +63,7 @@ public struct Entity {
/// - Parameter components: one or more components.
@discardableResult
public func assign(_ components: Component...) -> Entity {
for component: Component in components {
assign(component)
}
assign(components)
return self
}

Expand All @@ -66,7 +79,13 @@ public struct Entity {
/// - Parameter component: the typed component.
@discardableResult
public func assign<C>(_ component: C) -> Entity where C: Component {
nexus.assign(component: component, to: self)
assign(component)
return self
}

@discardableResult
public func assign<C>(_ components: C) -> Entity where C: Collection, C.Element == Component {
nexus.assign(components: components, to: self)
return self
}

Expand Down Expand Up @@ -107,29 +126,21 @@ public struct Entity {
public func makeComponentsIterator() -> ComponentsIterator {
ComponentsIterator(nexus: nexus, entityIdentifier: identifier)
}

/// Returns a sequence of all componenents of this entity.
@inlinable
public func allComponents() -> AnySequence<Component> {
AnySequence { self.makeComponentsIterator() }
}
}

extension Entity {
public struct ComponentsIterator: IteratorProtocol {
private var iterator: AnyIterator<Component>
private var iterator: IndexingIterator<([Component])>?

@usableFromInline
init(nexus: Nexus, entityIdentifier: EntityIdentifier) {
if let comps = nexus.get(components: entityIdentifier) {
iterator = AnyIterator<Component>(comps.compactMap { nexus.get(unsafe: $0, for: entityIdentifier) }.makeIterator())
} else {
iterator = AnyIterator { nil }
}
iterator = nexus.get(components: entityIdentifier)?
.map { nexus.get(unsafe: $0, for: entityIdentifier) }
.makeIterator()
}

public mutating func next() -> Component? {
iterator.next()
iterator?.next()
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions Sources/FirebladeECS/Nexus+Component.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ extension Nexus {
assign(component: component, to: entity)
}

@discardableResult
public final func assign<C>(components: C, to entity: Entity) -> Bool where C: Collection, C.Element == Component {
assign(components: components, to: entity.identifier)
}

@inlinable
public final func get(safe componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component? {
guard let uniformComponents = componentsByType[componentId], uniformComponents.contains(entityId.index) else {
Expand Down
6 changes: 4 additions & 2 deletions Sources/FirebladeECS/Nexus+Internal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@

extension Nexus {
@usableFromInline
func assign<C>(components: C, to entityId: EntityIdentifier) where C: Collection, C.Element == Component {
@discardableResult
func assign<C>(components: C, to entityId: EntityIdentifier) -> Bool where C: Collection, C.Element == Component {
var iter = components.makeIterator()
while let component = iter.next() {
let componentId = component.identifier
// test if component is already assigned
guard !has(componentId: componentId, entityId: entityId) else {
delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)")
assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)")
return
return false
}

// add component instances to uniform component stores
Expand All @@ -27,6 +28,7 @@ extension Nexus {

// Update entity membership
update(familyMembership: entityId)
return true
}

@usableFromInline
Expand Down
23 changes: 23 additions & 0 deletions Tests/FirebladeECSTests/Base.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ import FirebladeECS
class EmptyComponent: Component {
}

final class Optionals: Component, DefaultInitializable {
var int: Int?
var float: Float?
var string: String?

convenience init() {
self.init(nil, nil, nil)
}

init(_ int: Int?, _ float: Float?, _ string: String?) {
self.int = int
self.float = float
self.string = string
}
}
extension Optionals: Equatable {
static func == (lhs: Optionals, rhs: Optionals) -> Bool {
lhs.int == rhs.int &&
lhs.float == rhs.float &&
lhs.string == rhs.string
}
}

final class Name: Component, DefaultInitializable {
var name: String
init(name: String) {
Expand Down
52 changes: 51 additions & 1 deletion Tests/FirebladeECSTests/EntityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class EntityTests: XCTestCase {
entity.assign(name, vel)

let expectedComponents: [Component] = [pos, name, vel]
let allComponents = entity.allComponents()
let allComponents = Array(entity.makeComponentsIterator())

XCTAssertTrue(allComponents.elementsEqualUnordered(expectedComponents) { $0 === $1 })
}
Expand Down Expand Up @@ -117,6 +117,56 @@ class EntityTests: XCTestCase {
entity[Position.self] = nil // remove position component
XCTAssertNil(entity[Position.self])

let opts = Optionals(1, 2, "hello")
entity[Optionals.self] = opts
XCTAssertEqual(entity[Optionals.self], opts)

entity[\Optionals.float] = nil
XCTAssertEqual(entity[\Optionals.float], nil)
XCTAssertEqual(entity[\Optionals.int], 1)
XCTAssertEqual(entity[\Optionals.string], "hello")

entity[Optionals.self] = nil
XCTAssertNil(entity[Optionals.self])
entity[\Optionals.string] = "world"
XCTAssertEqual(entity[\Optionals.string], "world")

entity.assign(Comp1(12))
XCTAssertEqual(entity[\Comp1.value], 12)
}

func testComponentsIteration() {
let nexus = Nexus()
let entity = nexus.createEntity()
XCTAssertTrue(Array(entity.makeComponentsIterator()).isEmpty)
entity.assign(Position())
XCTAssertEqual(Array(entity.makeComponentsIterator()).count, 1)
}

func testEntityCreationIntrinsic() {
let nexus = Nexus()
let entity = nexus.createEntity()

let secondEntity = entity.createEntity()
XCTAssertNotEqual(secondEntity, entity)

let thirdEntity = secondEntity.createEntity()
XCTAssertNotEqual(secondEntity, thirdEntity)
XCTAssertNotEqual(entity, thirdEntity)

let entityWithComponents = entity.createEntity(with: Position(), Name())
XCTAssertTrue(entityWithComponents.has(Position.self))
XCTAssertTrue(entityWithComponents.has(Name.self))

XCTAssertEqual(nexus.numEntities, 4)
XCTAssertEqual(nexus.numComponents, 2)
}

func testEntityDescriptions() {
let nexus = Nexus()
let entt = nexus.createEntity()
XCTAssertFalse(entt.description.isEmpty)
XCTAssertFalse(entt.debugDescription.isEmpty)
}
}

Expand Down
3 changes: 3 additions & 0 deletions Tests/FirebladeECSTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ extension EntityTests {
// to regenerate.
static let __allTests__EntityTests = [
("testAllComponentsOfEntity", testAllComponentsOfEntity),
("testComponentsIteration", testComponentsIteration),
("testEntityCreationIntrinsic", testEntityCreationIntrinsic),
("testEntityDescriptions", testEntityDescriptions),
("testEntityEquality", testEntityEquality),
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex),
("testEntityIdGenerator", testEntityIdGenerator),
Expand Down

0 comments on commit 78f690c

Please sign in to comment.