Merge branch 'feature/obsolete-relatives' into develop
This commit is contained in:
commit
680ab42176
31
README.md
31
README.md
|
|
@ -203,37 +203,6 @@ class GameLogicSystem {
|
|||
|
||||
```
|
||||
|
||||
### 👫 Relatives (deprecated)
|
||||
|
||||
This ECS implementation provides an integrated way of creating a [directed acyclic graph (DAG)](https://en.wikipedia.org/wiki/Directed_acyclic_graph) hierarchy of entities by forming parent-child relationships. Entities can become children of a parent entity. In family terms they become **relatives**. Families provide iteration over these relationships.
|
||||
The entity hierarchy implementation does not use an additional component therefore keeping the hierarchy intact over different component-families.
|
||||
This feature is especially useful for implementing a [scene graph](https://en.wikipedia.org/wiki/Scene_graph).
|
||||
|
||||
```swift
|
||||
// create entities with 0 to n components
|
||||
let parent: Entity = nexus.createEntity(with: Position(x: 1, y: 1), SomeOtherComponent(...))
|
||||
let child: Entity = nexus.createEntity(with: Position(x: 2, y: 2))
|
||||
let child2: Entity = nexus.createEntity(with: Position(x: 3, y: 3), MySpecialComponent(...))
|
||||
|
||||
// create relationships between entities
|
||||
parent.addChild(child)
|
||||
child.addChild(child2)
|
||||
// or remove them
|
||||
// parent.removeChild(child)
|
||||
|
||||
// iterate over component families descending the graph
|
||||
nexus.family(requires: Position.self)
|
||||
.descendRelatives(from: parent) // provide the start entity (aka root "node")
|
||||
.forEach { (parent: Position, child: Position) in
|
||||
// parent: the current parent component
|
||||
// child: the current child component
|
||||
|
||||
// update your components hierarchically
|
||||
child.x += parent.x
|
||||
child.y += parent.y
|
||||
}
|
||||
```
|
||||
|
||||
### 🔗 Serialization
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -102,34 +102,6 @@ public struct Entity {
|
|||
nexus.destroy(entity: self)
|
||||
}
|
||||
|
||||
/// Add an entity as child.
|
||||
/// - Parameter entity: The child entity.
|
||||
@discardableResult
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public func addChild(_ entity: Entity) -> Bool {
|
||||
nexus.addChild(entity, to: self)
|
||||
}
|
||||
|
||||
/// Remove entity as child.
|
||||
/// - Parameter entity: The child entity.
|
||||
@discardableResult
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public func removeChild(_ entity: Entity) -> Bool {
|
||||
nexus.removeChild(entity, from: self)
|
||||
}
|
||||
|
||||
/// Removes all children from this entity.
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public func removeAllChildren() {
|
||||
nexus.removeAllChildren(from: self)
|
||||
}
|
||||
|
||||
/// Returns the number of children for this entity.
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public var numChildren: Int {
|
||||
nexus.numChildren(for: self)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all components of this entity.
|
||||
@inlinable
|
||||
public func makeComponentsIterator() -> ComponentsIterator {
|
||||
|
|
|
|||
|
|
@ -130,61 +130,6 @@ extension Family {
|
|||
|
||||
extension Family.EntityComponentIterator: LazySequenceProtocol { }
|
||||
|
||||
// MARK: - relatives iterator
|
||||
|
||||
extension Family {
|
||||
@inlinable
|
||||
public func descendRelatives(from root: Entity) -> RelativesIterator {
|
||||
RelativesIterator(family: self, root: root)
|
||||
}
|
||||
|
||||
public struct RelativesIterator: IteratorProtocol {
|
||||
@usableFromInline unowned let nexus: Nexus
|
||||
@usableFromInline let familyTraits: FamilyTraitSet
|
||||
|
||||
@usableFromInline var relatives: ContiguousArray<(EntityIdentifier, EntityIdentifier)>
|
||||
|
||||
public init(family: Family<R>, root: Entity) {
|
||||
self.nexus = family.nexus
|
||||
self.familyTraits = family.traits
|
||||
|
||||
// FIXME: this is not the most efficient way to aggregate all parent child tuples
|
||||
// Problems:
|
||||
// - allocates new memory
|
||||
// - needs to be build on every iteration
|
||||
// - relies on isMember check
|
||||
self.relatives = []
|
||||
self.relatives.reserveCapacity(family.memberIds.count)
|
||||
aggregateRelativesBreathFirst(root.identifier)
|
||||
relatives.reverse()
|
||||
}
|
||||
|
||||
mutating func aggregateRelativesBreathFirst(_ parent: EntityIdentifier) {
|
||||
guard let children = nexus.childrenByParentEntity[parent] else {
|
||||
return
|
||||
}
|
||||
children
|
||||
.compactMap { child in
|
||||
guard nexus.isMember(child, in: familyTraits) else {
|
||||
return nil
|
||||
}
|
||||
relatives.append((parent, child))
|
||||
return child
|
||||
}
|
||||
.forEach { aggregateRelativesBreathFirst($0) }
|
||||
}
|
||||
|
||||
public mutating func next() -> R.RelativesDescending? {
|
||||
guard let (parentId, childId) = relatives.popLast() else {
|
||||
return nil
|
||||
}
|
||||
return R.relativesDescending(nexus: nexus, parentId: parentId, childId: childId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Family.RelativesIterator: LazySequenceProtocol { }
|
||||
|
||||
// MARK: - member creation
|
||||
extension Family {
|
||||
/// Create a new entity with components required by this family.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ public protocol FamilyRequirementsManaging {
|
|||
associatedtype Components
|
||||
associatedtype ComponentTypes
|
||||
associatedtype EntityAndComponents
|
||||
associatedtype RelativesDescending
|
||||
|
||||
init(_ types: ComponentTypes)
|
||||
|
||||
|
|
@ -17,7 +16,5 @@ 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,8 +60,6 @@ extension Nexus {
|
|||
return false
|
||||
}
|
||||
|
||||
removeAllChildren(from: entityId)
|
||||
|
||||
if removeAll(components: entityId) {
|
||||
update(familyMembership: entityId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
//
|
||||
// Nexus+SceneGraph.swift
|
||||
//
|
||||
//
|
||||
// Created by Christian Treffs on 30.09.19.
|
||||
//
|
||||
|
||||
extension Nexus {
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public final func addChild(_ child: Entity, to parent: Entity) -> Bool {
|
||||
let inserted: Bool
|
||||
if childrenByParentEntity[parent.identifier] == nil {
|
||||
childrenByParentEntity[parent.identifier] = [child.identifier]
|
||||
inserted = true
|
||||
} else {
|
||||
let (isNewMember, _) = childrenByParentEntity[parent.identifier]!.insert(child.identifier)
|
||||
inserted = isNewMember
|
||||
}
|
||||
if inserted {
|
||||
delegate?.nexusEvent(ChildAdded(parent: parent.identifier, child: child.identifier))
|
||||
}
|
||||
return inserted
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public final func removeChild(_ child: Entity, from parent: Entity) -> Bool {
|
||||
removeChild(child.identifier, from: parent.identifier)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
@discardableResult
|
||||
public final func removeChild(_ child: EntityIdentifier, from parent: EntityIdentifier) -> Bool {
|
||||
let removed: Bool = childrenByParentEntity[parent]?.remove(child) != nil
|
||||
if removed {
|
||||
delegate?.nexusEvent(ChildRemoved(parent: parent, child: child))
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public final func removeAllChildren(from parent: Entity) {
|
||||
self.removeAllChildren(from: parent.identifier)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public final func removeAllChildren(from parentId: EntityIdentifier) {
|
||||
childrenByParentEntity[parentId]?.forEach { removeChild($0, from: parentId) }
|
||||
return childrenByParentEntity[parentId] = nil
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
public final func numChildren(for entity: Entity) -> Int {
|
||||
childrenByParentEntity[entity.identifier]?.count ?? 0
|
||||
}
|
||||
}
|
||||
|
|
@ -23,10 +23,6 @@ public final class Nexus {
|
|||
/// Each element is a component identifier associated with this entity.
|
||||
@usableFromInline final var componentIdsByEntity: [EntityIdentifier: Set<ComponentIdentifier>]
|
||||
|
||||
/// - Key: A parent entity id.
|
||||
/// - Value: Adjacency Set of all associated children.
|
||||
@usableFromInline final var childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>]
|
||||
|
||||
/// - Key: FamilyTraitSet aka component types that make up one distinct family.
|
||||
/// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family.
|
||||
@usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>]
|
||||
|
|
@ -41,7 +37,6 @@ public final class Nexus {
|
|||
componentsByEntity: [:],
|
||||
entityIdGenerator: EntityIdentifierGenerator(),
|
||||
familyMembersByTraits: [:],
|
||||
childrenByParentEntity: [:],
|
||||
codingStrategy: DefaultCodingStrategy())
|
||||
}
|
||||
|
||||
|
|
@ -50,13 +45,11 @@ public final class Nexus {
|
|||
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
||||
entityIdGenerator: EntityIdentifierGenerator,
|
||||
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>],
|
||||
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
|
||||
}
|
||||
|
|
@ -71,7 +64,6 @@ public final class Nexus {
|
|||
componentsByType.removeAll()
|
||||
componentIdsByEntity.removeAll()
|
||||
familyMembersByTraits.removeAll()
|
||||
childrenByParentEntity.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
//
|
||||
// SceneGraphTests.swift
|
||||
// FirebladeECSTests
|
||||
//
|
||||
// Created by Christian Treffs on 30.09.19.
|
||||
//
|
||||
|
||||
import FirebladeECS
|
||||
import XCTest
|
||||
|
||||
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||
class SceneGraphTests: XCTestCase {
|
||||
var nexus: Nexus!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
nexus = Nexus()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
nexus = nil
|
||||
}
|
||||
|
||||
func testAddChild() {
|
||||
let nttParrent = nexus.createEntity()
|
||||
let nttChild1 = nexus.createEntity()
|
||||
XCTAssertEqual(nttParrent.numChildren, 0)
|
||||
XCTAssertTrue(nttParrent.addChild(nttChild1))
|
||||
XCTAssertEqual(nttParrent.numChildren, 1)
|
||||
XCTAssertFalse(nttParrent.addChild(nttChild1))
|
||||
XCTAssertEqual(nttParrent.numChildren, 1)
|
||||
}
|
||||
|
||||
func testRemoveChild() {
|
||||
let nttParrent = nexus.createEntity()
|
||||
let nttChild1 = nexus.createEntity()
|
||||
|
||||
XCTAssertEqual(nttParrent.numChildren, 0)
|
||||
XCTAssertTrue(nttParrent.addChild(nttChild1))
|
||||
XCTAssertEqual(nttParrent.numChildren, 1)
|
||||
XCTAssertTrue(nttParrent.removeChild(nttChild1))
|
||||
XCTAssertEqual(nttParrent.numChildren, 0)
|
||||
XCTAssertFalse(nttParrent.removeChild(nttChild1))
|
||||
XCTAssertEqual(nttParrent.numChildren, 0)
|
||||
XCTAssertTrue(nttParrent.addChild(nttChild1))
|
||||
XCTAssertEqual(nttParrent.numChildren, 1)
|
||||
}
|
||||
|
||||
func testRemoveAllChildren() {
|
||||
let nttParrent = nexus.createEntity()
|
||||
let nttChild1 = nexus.createEntity()
|
||||
let nttChild2 = nexus.createEntity()
|
||||
|
||||
XCTAssertEqual(nttParrent.numChildren, 0)
|
||||
nttParrent.addChild(nttChild1)
|
||||
nttParrent.addChild(nttChild2)
|
||||
XCTAssertEqual(nttParrent.numChildren, 2)
|
||||
|
||||
nttParrent.removeAllChildren()
|
||||
XCTAssertEqual(nttParrent.numChildren, 0)
|
||||
}
|
||||
|
||||
func testDescendRelativesSimple() {
|
||||
let nttParrent = nexus.createEntity(with: Position(x: 1, y: 1))
|
||||
let child1Pos = Position(x: 2, y: 2)
|
||||
let nttChild1 = nexus.createEntity(with: child1Pos)
|
||||
|
||||
nttParrent.addChild(nttChild1)
|
||||
|
||||
let family = nexus.family(requires: Position.self)
|
||||
|
||||
var counter: Int = 0
|
||||
|
||||
XCTAssertEqual(child1Pos.x, 2)
|
||||
XCTAssertEqual(child1Pos.y, 2)
|
||||
|
||||
family
|
||||
.descendRelatives(from: nttParrent)
|
||||
.forEach { (parent: Position, child: Position) in
|
||||
defer { counter += 1 }
|
||||
|
||||
child.x += parent.x
|
||||
child.y += parent.y
|
||||
}
|
||||
|
||||
XCTAssertEqual(counter, 1)
|
||||
XCTAssertEqual(child1Pos.x, 3)
|
||||
XCTAssertEqual(child1Pos.y, 3)
|
||||
}
|
||||
|
||||
func testDescendRelativesOnlyFamilyMembers() {
|
||||
let otherComponents: [Component] = [Position(x: 0, y: 0), Velocity(a: 0), Party(partying: true), Color(), Name(name: "")]
|
||||
|
||||
func addChild(to parent: Entity, index: Int) -> Entity {
|
||||
let randComp = otherComponents.randomElement()!
|
||||
let badChild = nexus.createEntity(with: randComp)
|
||||
let child = nexus.createEntity(with: Index(index: index))
|
||||
parent.addChild(child)
|
||||
parent.addChild(badChild)
|
||||
return child
|
||||
}
|
||||
|
||||
let root = nexus.createEntity(with: Index(index: 0))
|
||||
|
||||
var parent: Entity = root
|
||||
for i in 1..<10 {
|
||||
parent = addChild(to: parent, index: i)
|
||||
}
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 19)
|
||||
|
||||
var parentSum: Int = 0
|
||||
var childSum: Int = 0
|
||||
|
||||
var lastIndex: Int = -1
|
||||
|
||||
nexus
|
||||
.family(requires: Index.self)
|
||||
.descendRelatives(from: root)
|
||||
.forEach { (parent: Index, child: Index) in
|
||||
XCTAssertEqual(parent.index + 1, child.index)
|
||||
XCTAssertGreaterThan(parent.index, lastIndex)
|
||||
lastIndex = parent.index
|
||||
parentSum += parent.index
|
||||
childSum += child.index
|
||||
}
|
||||
|
||||
XCTAssertEqual(parentSum, 36)
|
||||
XCTAssertEqual(childSum, 45)
|
||||
}
|
||||
}
|
||||
|
|
@ -235,19 +235,6 @@ extension NexusTests {
|
|||
]
|
||||
}
|
||||
|
||||
extension SceneGraphTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__SceneGraphTests = [
|
||||
("testAddChild", testAddChild),
|
||||
("testDescendRelativesOnlyFamilyMembers", testDescendRelativesOnlyFamilyMembers),
|
||||
("testDescendRelativesSimple", testDescendRelativesSimple),
|
||||
("testRemoveAllChildren", testRemoveAllChildren),
|
||||
("testRemoveChild", testRemoveChild)
|
||||
]
|
||||
}
|
||||
|
||||
extension SingleTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
|
|
@ -310,7 +297,6 @@ public func __allTests() -> [XCTestCaseEntry] {
|
|||
testCase(FamilyTraitsTests.__allTests__FamilyTraitsTests),
|
||||
testCase(HashingTests.__allTests__HashingTests),
|
||||
testCase(NexusTests.__allTests__NexusTests),
|
||||
testCase(SceneGraphTests.__allTests__SceneGraphTests),
|
||||
testCase(SingleTests.__allTests__SingleTests),
|
||||
testCase(SparseSetTests.__allTests__SparseSetTests),
|
||||
testCase(SystemsTests.__allTests__SystemsTests)
|
||||
|
|
|
|||
Loading…
Reference in New Issue