Merge branch 'feature/bulk-create-entities' into develop

This commit is contained in:
Christian Treffs 2020-07-30 16:34:24 +02:00
commit 242f7993d6
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
5 changed files with 216 additions and 8 deletions

View File

@ -66,24 +66,41 @@ let nexus = Nexus()
then create entities by letting the `Nexus` generate them.
```swift
let myEntity = nexus.createEntity()
// an entity without components
let newEntity = nexus.createEntity()
```
You can define `Components` like this
To define components conform your class to the `Component` protocol
```swift
class Movement: Component {
var position: (x: Double, y: Double) = (0.0, 1.0)
var velocity: Double = 0.1
final class Position: Component {
var x: Int = 0
var y: Int = 0
}
```
and assign instances of them to an `Entity` with
and assign instances of it to an `Entity` with
```swift
let movement = Movement()
myEntity.assign(movement)
let position = Position(x: 1, y: 2)
entity.assign(position)
```
You can be more efficient by assigning components while creating an entity.
```swift
// an entity with two components assigned.
nexus.createEntity {
Position(x: 1, y: 2)
Color(.red)
}
// bulk create entities with multiple components assigned.
nexus.createEntities(count: 100) { _ in
Position()
Color()
}
```
### 👪 Families
This ECS uses a grouping approach for entities with the same component types to optimize cache locality and ease up access to them.

View File

@ -0,0 +1,91 @@
//
// Nexus+ComponentsBuilder.swift
//
//
// Created by Christian Treffs on 30.07.20.
//
@_functionBuilder
public enum ComponentsBuilderPreview { }
public typealias ComponentsBuilder = ComponentsBuilderPreview
extension ComponentsBuilder {
public static func buildBlock(_ components: Component...) -> [Component] {
components
}
public struct Context {
/// The index of the newly created entity.
///
/// This is **NOT** equal to the entity identifier.
public let index: Int
}
}
extension Nexus {
/// Create an entity assigning one component.
///
/// Usage:
/// ```
/// let newEntity = nexus.createEntity {
/// Position(x: 1, y: 2)
/// }
/// ```
/// - Parameter builder: The component builder.
/// - Returns: The newly created entity with the provided component assigned.
@discardableResult
public func createEntity(@ComponentsBuilder using builder: () -> Component) -> Entity {
self.createEntity(with: builder())
}
/// Create an entity assigning multiple components.
///
/// Usage:
/// ```
/// let newEntity = nexus.createEntity {
/// Position(x: 1, y: 2)
/// Name(name: "Some name")
/// }
/// ```
/// - Parameter builder: The component builder.
/// - Returns: The newly created entity with the provided components assigned.
@discardableResult
public func createEntity(@ComponentsBuilder using builder: () -> [Component]) -> Entity {
self.createEntity(with: builder())
}
/// Create multiple entities assigning one component each.
///
/// Usage:
/// ```
/// let newEntities = nexus.createEntities(count: 100) { ctx in
/// Velocity(a: Float(ctx.index))
/// }
/// ```
/// - Parameters:
/// - count: The count of entities to create.
/// - builder: The component builder providing context.
/// - Returns: The newly created entities with the provided component assigned.
@discardableResult
public func createEntities(count: Int, @ComponentsBuilder using builder: (ComponentsBuilder.Context) -> Component) -> [Entity] {
(0..<count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
}
/// Create multiple entities assigning multiple components each.
///
/// Usage:
/// ```
/// let newEntities = nexus.createEntities(count: 100) { ctx in
/// Position(x: ctx.index, y: ctx.index)
/// Name(name: "\(ctx.index)")
/// }
/// ```
/// - Parameters:
/// - count: The count of entities to create.
/// - builder: The component builder providing context.
/// - Returns: The newly created entities with the provided components assigned.
@discardableResult
public func createEntities(count: Int, @ComponentsBuilder using builder: (ComponentsBuilder.Context) -> [Component]) -> [Entity] {
(0..<count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
}
}

View File

@ -21,6 +21,13 @@ extension Nexus {
return newEntity
}
@discardableResult
public func createEntity<C>(with components: C) -> Entity where C: Collection, C.Element == Component {
let entity = self.createEntity()
components.forEach { entity.assign($0) }
return entity
}
/// Number of entities in nexus.
public var numEntities: Int {
entityStorage.count

View File

@ -0,0 +1,80 @@
//
// EntityCreationTests.swift
//
//
// Created by Christian Treffs on 30.07.20.
//
import FirebladeECS
import XCTest
final class EntityCreationTests: XCTestCase {
func testCreateEntityOneComponent() throws {
let nexus = Nexus()
let entity = nexus.createEntity {
Position(x: 1, y: 2)
}
XCTAssertEqual(entity[\Position.x], 1)
XCTAssertEqual(entity[\Position.y], 2)
XCTAssertEqual(nexus.numEntities, 1)
XCTAssertEqual(nexus.numComponents, 1)
XCTAssertEqual(nexus.numFamilies, 0)
}
func testCreateEntityMultipleComponents() throws {
let nexus = Nexus()
let entity = nexus.createEntity {
Position(x: 1, y: 2)
Name(name: "Hello")
}
XCTAssertEqual(entity[\Position.x], 1)
XCTAssertEqual(entity[\Position.y], 2)
XCTAssertEqual(entity[\Name.name], "Hello")
XCTAssertEqual(nexus.numEntities, 1)
XCTAssertEqual(nexus.numComponents, 2)
XCTAssertEqual(nexus.numFamilies, 0)
}
func testBulkCreateEntitiesOneComponent() throws {
let nexus = Nexus()
let entities = nexus.createEntities(count: 100) { ctx in
Velocity(a: Float(ctx.index))
}
XCTAssertEqual(entities[0][\Velocity.a], 0)
XCTAssertEqual(entities[99][\Velocity.a], 99)
XCTAssertEqual(nexus.numEntities, 100)
XCTAssertEqual(nexus.numComponents, 100)
XCTAssertEqual(nexus.numFamilies, 0)
}
func testBulkCreateEntitiesMultipleComponents() throws {
let nexus = Nexus()
let entities = nexus.createEntities(count: 100) { ctx in
Position(x: ctx.index, y: ctx.index)
Name(name: "\(ctx.index)")
}
XCTAssertEqual(entities[0][\Position.x], 0)
XCTAssertEqual(entities[0][\Position.y], 0)
XCTAssertEqual(entities[0][\Name.name], "0")
XCTAssertEqual(entities[99][\Position.x], 99)
XCTAssertEqual(entities[99][\Position.y], 99)
XCTAssertEqual(entities[99][\Name.name], "99")
XCTAssertEqual(nexus.numEntities, 100)
XCTAssertEqual(nexus.numComponents, 200)
XCTAssertEqual(nexus.numFamilies, 0)
}
}

View File

@ -20,6 +20,18 @@ extension ComponentTests {
]
}
extension EntityCreationTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EntityCreationTests = [
("testBulkCreateEntitiesMultipleComponents", testBulkCreateEntitiesMultipleComponents),
("testBulkCreateEntitiesOneComponent", testBulkCreateEntitiesOneComponent),
("testCreateEntityMultipleComponents", testCreateEntityMultipleComponents),
("testCreateEntityOneComponent", testCreateEntityOneComponent)
]
}
extension EntityTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
@ -162,6 +174,7 @@ public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
testCase(ComponentTests.__allTests__ComponentTests),
testCase(EntityCreationTests.__allTests__EntityCreationTests),
testCase(EntityTests.__allTests__EntityTests),
testCase(FamilyCodingTests.__allTests__FamilyCodingTests),
testCase(FamilyTests.__allTests__FamilyTests),