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
|
### 🔗 Serialization
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,34 +102,6 @@ public struct Entity {
|
||||||
nexus.destroy(entity: self)
|
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.
|
/// Returns an iterator over all components of this entity.
|
||||||
@inlinable
|
@inlinable
|
||||||
public func makeComponentsIterator() -> ComponentsIterator {
|
public func makeComponentsIterator() -> ComponentsIterator {
|
||||||
|
|
|
||||||
|
|
@ -130,61 +130,6 @@ extension Family {
|
||||||
|
|
||||||
extension Family.EntityComponentIterator: LazySequenceProtocol { }
|
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
|
// MARK: - member creation
|
||||||
extension Family {
|
extension Family {
|
||||||
/// Create a new entity with components required by this family.
|
/// Create a new entity with components required by this family.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ public protocol FamilyRequirementsManaging {
|
||||||
associatedtype Components
|
associatedtype Components
|
||||||
associatedtype ComponentTypes
|
associatedtype ComponentTypes
|
||||||
associatedtype EntityAndComponents
|
associatedtype EntityAndComponents
|
||||||
associatedtype RelativesDescending
|
|
||||||
|
|
||||||
init(_ types: ComponentTypes)
|
init(_ types: ComponentTypes)
|
||||||
|
|
||||||
|
|
@ -17,7 +16,5 @@ public protocol FamilyRequirementsManaging {
|
||||||
|
|
||||||
static func components(nexus: Nexus, entityId: EntityIdentifier) -> Components
|
static func components(nexus: Nexus, entityId: EntityIdentifier) -> Components
|
||||||
static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> EntityAndComponents
|
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
|
static func createMember(nexus: Nexus, components: Components) -> Entity
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,6 @@ extension Nexus {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllChildren(from: entityId)
|
|
||||||
|
|
||||||
if removeAll(components: entityId) {
|
if removeAll(components: entityId) {
|
||||||
update(familyMembership: 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.
|
/// Each element is a component identifier associated with this entity.
|
||||||
@usableFromInline final var componentIdsByEntity: [EntityIdentifier: Set<ComponentIdentifier>]
|
@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.
|
/// - 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.
|
/// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family.
|
||||||
@usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>]
|
@usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>]
|
||||||
|
|
@ -41,7 +37,6 @@ public final class Nexus {
|
||||||
componentsByEntity: [:],
|
componentsByEntity: [:],
|
||||||
entityIdGenerator: EntityIdentifierGenerator(),
|
entityIdGenerator: EntityIdentifierGenerator(),
|
||||||
familyMembersByTraits: [:],
|
familyMembersByTraits: [:],
|
||||||
childrenByParentEntity: [:],
|
|
||||||
codingStrategy: DefaultCodingStrategy())
|
codingStrategy: DefaultCodingStrategy())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,13 +45,11 @@ public final class Nexus {
|
||||||
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
||||||
entityIdGenerator: EntityIdentifierGenerator,
|
entityIdGenerator: EntityIdentifierGenerator,
|
||||||
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>],
|
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>],
|
||||||
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>],
|
|
||||||
codingStrategy: CodingStrategy) {
|
codingStrategy: CodingStrategy) {
|
||||||
self.entityStorage = entityStorage
|
self.entityStorage = entityStorage
|
||||||
self.componentsByType = componentsByType
|
self.componentsByType = componentsByType
|
||||||
self.componentIdsByEntity = componentsByEntity
|
self.componentIdsByEntity = componentsByEntity
|
||||||
self.familyMembersByTraits = familyMembersByTraits
|
self.familyMembersByTraits = familyMembersByTraits
|
||||||
self.childrenByParentEntity = childrenByParentEntity
|
|
||||||
self.entityIdGenerator = entityIdGenerator
|
self.entityIdGenerator = entityIdGenerator
|
||||||
self.codingStrategy = codingStrategy
|
self.codingStrategy = codingStrategy
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +64,6 @@ public final class Nexus {
|
||||||
componentsByType.removeAll()
|
componentsByType.removeAll()
|
||||||
componentIdsByEntity.removeAll()
|
componentIdsByEntity.removeAll()
|
||||||
familyMembersByTraits.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 {
|
extension SingleTests {
|
||||||
// DO NOT MODIFY: This is autogenerated, use:
|
// DO NOT MODIFY: This is autogenerated, use:
|
||||||
// `swift test --generate-linuxmain`
|
// `swift test --generate-linuxmain`
|
||||||
|
|
@ -310,7 +297,6 @@ public func __allTests() -> [XCTestCaseEntry] {
|
||||||
testCase(FamilyTraitsTests.__allTests__FamilyTraitsTests),
|
testCase(FamilyTraitsTests.__allTests__FamilyTraitsTests),
|
||||||
testCase(HashingTests.__allTests__HashingTests),
|
testCase(HashingTests.__allTests__HashingTests),
|
||||||
testCase(NexusTests.__allTests__NexusTests),
|
testCase(NexusTests.__allTests__NexusTests),
|
||||||
testCase(SceneGraphTests.__allTests__SceneGraphTests),
|
|
||||||
testCase(SingleTests.__allTests__SingleTests),
|
testCase(SingleTests.__allTests__SingleTests),
|
||||||
testCase(SparseSetTests.__allTests__SparseSetTests),
|
testCase(SparseSetTests.__allTests__SparseSetTests),
|
||||||
testCase(SystemsTests.__allTests__SystemsTests)
|
testCase(SystemsTests.__allTests__SystemsTests)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue