Merge branch 'master' into develop

This commit is contained in:
Christian Treffs 2020-10-08 10:15:09 +02:00
commit ea8b88b352
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
3 changed files with 1086 additions and 0 deletions

View File

@ -0,0 +1,429 @@
//
// FSM.swift
// FirebladeECS
//
// Created by Igor Kravchenko on 29.09.2020.
//
/// Requires initializer with default values.
/// In case of component - makes sure it can be instantiated by component provider
public protocol DefaultInitializable {
init()
}
public typealias ComponentInitializable = Component & DefaultInitializable
/// This is the Interface for component providers. Component providers are used to supply components
/// for states within an EntityStateMachine. FirebladeECS includes three standard component providers,
/// ComponentTypeProvider, ComponentInstanceProvider and ComponentSingletonProvider. Developers
/// may wish to create more.
public protocol ComponentProvider {
/// Returns an identifier that is used to determine whether two component providers will
/// return the equivalent components.
/// If an entity is changing state and the state it is leaving and the state is
/// entering have components of the same type, then the identifiers of the component
/// provders are compared. If the two identifiers are the same then the component
/// is not removed. If they are different, the component from the old state is removed
/// and a component for the new state is added.
/// - Returns: struct/class instance that conforms to Hashable protocol
var identifier: AnyHashable { get }
/// Used to request a component from the provider.
/// - Returns: A component for use in the state that the entity is entering
func getComponent() -> Component
}
// MARK: -
/// This component provider always returns the same instance of the component. The instance
/// is passed to the provider at initialisation.
public final class ComponentInstanceProvider {
private var instance: Component
/// Initializer
/// - Parameter instance: The instance to return whenever a component is requested.
public init(instance: Component) {
self.instance = instance
}
}
extension ComponentInstanceProvider: ComponentProvider {
/// Used to compare this provider with others. Any provider that returns the same component
/// instance will be regarded as equivalent.
/// - Returns:ObjectIdentifier of instance
public var identifier: AnyHashable {
ObjectIdentifier(instance)
}
/// Used to request a component from this provider
/// - Returns: The instance
public func getComponent() -> Component {
instance
}
}
// MARK: -
/// This component provider always returns a new instance of a component. An instance
/// is created when requested and is of the type passed in to the initializer.
public final class ComponentTypeProvider {
private var componentType: ComponentInitializable.Type
/// Used to compare this provider with others. Any ComponentTypeProvider that returns
/// the same type will be regarded as equivalent.
/// - Returns:ObjectIdentifier of the type of the instances created
public let identifier: AnyHashable
/// Initializer
/// - Parameter type: The type of the instances to be created
public init(type: ComponentInitializable.Type) {
componentType = type
identifier = ObjectIdentifier(componentType.self)
}
}
extension ComponentTypeProvider: ComponentProvider {
/// Used to request a component from this provider
/// - Returns: A new instance of the type provided in the initializer
public func getComponent() -> Component {
componentType.init()
}
}
// MARK: -
/// This component provider always returns the same instance of the component. The instance
/// is created when first required and is of the type passed in to the initializer.
public final class ComponentSingletonProvider {
private lazy var instance: Component = {
componentType.init()
}()
private var componentType: ComponentInitializable.Type
/// Initializer
/// - Parameter type: The type of the single instance
public init(type: ComponentInitializable.Type) {
componentType = type
}
}
extension ComponentSingletonProvider: ComponentProvider {
/// Used to compare this provider with others. Any provider that returns the same single
/// instance will be regarded as equivalent.
/// - Returns: ObjectIdentifier of the single instance
public var identifier: AnyHashable {
ObjectIdentifier(instance)
}
/// Used to request a component from this provider
/// - Returns: The single instance
public func getComponent() -> Component {
instance
}
}
// MARK: -
/// This component provider calls a function to get the component instance. The function must
/// return a single component of the appropriate type.
public final class DynamicComponentProvider<C: Component> {
/// Wrapper for closure to make it hashable via ObjectIdentifier
public final class Closure {
let provideComponent: () -> C
/// Initializer
/// - Parameter provideComponent: Swift closure returning component of the appropriate type
public init(provideComponent: @escaping () -> C) {
self.provideComponent = provideComponent
}
}
private let closure: Closure
/// Initializer
/// - Parameter closure: Instance of Closure class. A wrapper around closure that will
/// return the component instance when called.
public init(closure: Closure) {
self.closure = closure
}
}
extension DynamicComponentProvider: ComponentProvider {
/// Used to compare this provider with others. Any provider that uses the function or method
/// closure to provide the instance is regarded as equivalent.
/// - Returns: ObjectIdentifier of closure
public var identifier: AnyHashable {
ObjectIdentifier(closure)
}
/// Used to request a component from this provider
/// - Returns: The instance returned by calling the closure
public func getComponent() -> Component {
closure.provideComponent()
}
}
// MARK: -
/// Represents a state for an EntityStateMachine. The state contains any number of ComponentProviders which
/// are used to add components to the entity when this state is entered.
public class EntityState {
internal var providers = [ComponentIdentifier: ComponentProvider]()
public init() {}
/// Add a new StateComponentMapping to this state. The mapping is a utility class that is used to
/// map a component type to the provider that provides the component.
/// - Parameter type: The type of component to be mapped
/// - Returns: The component mapping to use when setting the provider for the component
@discardableResult
public func addMapping(for type: ComponentInitializable.Type) -> StateComponentMapping {
StateComponentMapping(creatingState: self, type: type)
}
/// Get the ComponentProvider for a particular component type.
/// - Parameter type: The type of component to get the provider for
/// - Returns: The ComponentProvider
public func provider(for type: ComponentInitializable.Type) -> ComponentProvider? {
providers[type.identifier]
}
/// To determine whether this state has a provider for a specific component type.
/// - Parameter type: The type of component to look for a provider for
/// - Returns: true if there is a provider for the given type, false otherwise
public func hasProvider(for type: ComponentInitializable.Type) -> Bool {
providers[type.identifier] != nil
}
}
/// This extension provides ergonomic way to add component mapping and component
/// provider at once
extension EntityState {
/// Creates a mapping for the component type to a specific component instance.
/// ComponentInstanceProvider is used for the mapping.
/// - Parameter component: The component instance to use for the mapping
/// - Returns: This EntityState, so more modifications can be applied
@discardableResult
@inline(__always)
public func addInstance<C: ComponentInitializable>(_ component: C) -> Self {
addMapping(for: C.self).withInstance(component)
return self
}
/// Creates a mapping for the component type to new instances of the provided type.
/// A ComponentTypeProvider is used for the mapping.
/// - Parameter type: The type of components to be created by this mapping
/// - Returns: This EntityState, so more modifications can be applied
@inline(__always)
@discardableResult
public func addType(_ type: ComponentInitializable.Type) -> Self {
addMapping(for: type).withType(type)
return self
}
/// Creates a mapping for the component type to a single instance of the provided type.
/// The instance is not created until it is first requested.
/// A ComponentSingletonProvider is used for the mapping.
/// - Parameter type: The type of the single instance to be created.
/// - Returns: This EntityState, so more modifications can be applied
@inline(__always)
@discardableResult
public func addSingleton(_ type: ComponentInitializable.Type) -> Self {
addMapping(for: type).withSingleton(type)
return self
}
/// Creates a mapping for the component type to a method call.
/// A DynamicComponentProvider is used for the mapping.
/// - Parameter closure: The Closure instance to return the component instance
/// - Returns: This EntityState, so more modifications can be applied
@inline(__always)
@discardableResult
public func addMethod<C: ComponentInitializable>(closure: DynamicComponentProvider<C>.Closure) -> Self {
addMapping(for: C.self).withMethod(closure)
return self
}
/// Creates a mapping for the component type to any ComponentProvider.
/// - Parameter type: The type of component to be mapped
/// - Parameter provider: The component provider to use.
/// - Returns: This EntityState, so more modifications can be applied.
@inline(__always)
@discardableResult
public func addProvider<C: ComponentInitializable>(type: C.Type, provider: ComponentProvider) -> Self {
addMapping(for: type).withProvider(provider)
return self
}
}
// MARK: -
/// Used by the EntityState class to create the mappings of components to providers via a fluent interface.
public class StateComponentMapping {
private var componentType: ComponentInitializable.Type
private let creatingState: EntityState
private var provider: ComponentProvider
/// Used internally, the initializer creates a component mapping. The constructor
/// creates a ComponentTypeProvider as the default mapping, which will be replaced
/// by more specific mappings if other methods are called.
/// - Parameter creatingState: The EntityState that the mapping will belong to
/// - Parameter type: The component type for the mapping
internal init(creatingState: EntityState, type: ComponentInitializable.Type) {
self.creatingState = creatingState
componentType = type
provider = ComponentTypeProvider(type: type)
setProvider(provider)
}
/// Creates a mapping for the component type to a specific component instance. A
/// ComponentInstanceProvider is used for the mapping.
/// - Parameter component: The component instance to use for the mapping
/// - Returns: This ComponentMapping, so more modifications can be applied
@discardableResult
public func withInstance(_ component: Component) -> StateComponentMapping {
setProvider(ComponentInstanceProvider(instance: component))
return self
}
/// Creates a mapping for the component type to new instances of the provided type.
/// The type should be the same as or extend the type for this mapping. A ComponentTypeProvider
/// is used for the mapping.
/// - Parameter type: The type of components to be created by this mapping
/// - Returns: This ComponentMapping, so more modifications can be applied
@discardableResult
public func withType(_ type: ComponentInitializable.Type) -> Self {
setProvider(ComponentTypeProvider(type: type))
return self
}
/// Creates a mapping for the component type to a single instance of the provided type.
/// The instance is not created until it is first requested. The type should be the same
/// as or extend the type for this mapping. A ComponentSingletonProvider is used for
/// the mapping.
/// - Parameter type: The type of the single instance to be created. If omitted, the type of the
/// mapping is used.
/// - Returns: This ComponentMapping, so more modifications can be applied
@discardableResult
public func withSingleton(_ type: ComponentInitializable.Type?) -> Self {
setProvider(ComponentSingletonProvider(type: type ?? componentType))
return self
}
/// Creates a mapping for the component type to a method call. A
/// DynamicComponentProvider is used for the mapping.
/// - Parameter closure: The Closure instance to return the component instance
/// - Returns: This ComponentMapping, so more modifications can be applied
@discardableResult
public func withMethod<C: Component>(_ closure: DynamicComponentProvider<C>.Closure) -> Self {
setProvider(DynamicComponentProvider(closure: closure))
return self
}
/// Creates a mapping for the component type to any ComponentProvider.
/// - Parameter provider: The component provider to use.
/// - Returns: This ComponentMapping, so more modifications can be applied.
@discardableResult
public func withProvider(_ provider: ComponentProvider) -> Self {
setProvider(provider)
return self
}
/// Maps through to the addMapping method of the EntityState that this mapping belongs to
/// so that a fluent interface can be used when configuring entity states.
/// - Parameter type: The type of component to add a mapping to the state for
/// - Returns: The new ComponentMapping for that type
@discardableResult
public func add(_ type: ComponentInitializable.Type) -> StateComponentMapping {
creatingState.addMapping(for: type)
}
private func setProvider(_ provider: ComponentProvider) {
self.provider = provider
creatingState.providers[componentType.identifier] = provider
}
}
// MARK: -
/// This is a state machine for an entity. The state machine manages a set of states,
/// each of which has a set of component providers. When the state machine changes the state, it removes
/// components associated with the previous state and adds components associated with the new state.
/// - Parameter StateIdentifier: Generic hashable state name type
public class EntityStateMachine<StateIdentifier: Hashable> {
private var states: [StateIdentifier: EntityState]
/// The current state of the state machine.
private var currentState: EntityState?
/// The entity whose state machine this is
public var entity: Entity
/// Initializer. Creates an EntityStateMachine.
public init(entity: Entity) {
self.entity = entity
states = [:]
}
/// Add a state to this state machine.
/// - Parameter name: The name of this state - used to identify it later in the changeState method call.
/// - Parameter state: The state.
/// - Returns: This state machine, so methods can be chained.
@discardableResult
public func addState(name: StateIdentifier, state: EntityState) -> Self {
states[name] = state
return self
}
/// Create a new state in this state machine.
/// - Parameter name: The name of the new state - used to identify it later in the changeState method call.
/// - Returns: The new EntityState object that is the state. This will need to be configured with
/// the appropriate component providers.
public func createState(name: StateIdentifier) -> EntityState {
let state = EntityState()
states[name] = state
return state
}
/// Change to a new state. The components from the old state will be removed and the components
/// for the new state will be added.
/// - Parameter name: The name of the state to change to.
public func changeState(name: StateIdentifier) {
guard let newState = states[name] else {
assertionFailure("Entity state '\(name)' doesn't exist")
return
}
if newState === currentState {
return
}
var toAdd: [ComponentIdentifier: ComponentProvider]
if let currentState = currentState {
toAdd = .init()
for (identifier, provider) in newState.providers {
toAdd[identifier] = provider
}
for (identifier, _) in currentState.providers {
if let other = toAdd[identifier], let current = currentState.providers[identifier],
current.identifier == other.identifier {
toAdd[identifier] = nil
} else {
entity.remove(identifier)
}
}
} else {
toAdd = newState.providers
}
for (_, provider) in toAdd {
entity.assign(provider.getComponent())
}
currentState = newState
}
}

View File

@ -0,0 +1,550 @@
//
// FSMTests.swift
// FirebladeECSTests
//
// Created by Igor Kravchenko on 29.09.2020.
//
import FirebladeECS
import XCTest
class ComponentInstanceProviderTests: XCTestCase {
func testProviderReturnsTheInstance() {
let instance = MockComponent(value: .max)
let provider1 = ComponentInstanceProvider(instance: instance)
let providedComponent = provider1.getComponent() as? MockComponent
XCTAssertTrue(providedComponent === instance)
}
func testProvidersWithSameInstanceHaveSameIdentifier() {
let instance = MockComponent(value: .max)
let provider1 = ComponentInstanceProvider(instance: instance)
let provider2 = ComponentInstanceProvider(instance: instance)
XCTAssertEqual(provider1.identifier, provider2.identifier)
}
func testProvidersWithDifferentInstanceHaveDifferentIdentifier() {
let provider1 = ComponentInstanceProvider(instance: MockComponent(value: .max))
let provider2 = ComponentInstanceProvider(instance: MockComponent(value: .max))
XCTAssertNotEqual(provider1.identifier, provider2.identifier)
}
class MockComponent: Component {
var value: Int
init(value: Int) {
self.value = value
}
}
}
// MARK: -
class ComponentTypeProviderTests: XCTestCase {
func testProviderReturnsAnInstanceOfType() {
let provider = ComponentTypeProvider(type: MockComponent.self)
let component = provider.getComponent() as? MockComponent
XCTAssertNotNil(component)
}
func testProviderReturnsNewInstanceEachTime() {
let provider = ComponentTypeProvider(type: MockComponent.self)
let component1 = provider.getComponent() as? MockComponent
let component2 = provider.getComponent() as? MockComponent
XCTAssertFalse(component1 === component2)
}
func testProvidersWithSameTypeHaveSameIdentifier() {
let provider1 = ComponentTypeProvider(type: MockComponent.self)
let provider2 = ComponentTypeProvider(type: MockComponent.self)
XCTAssertEqual(provider1.identifier, provider2.identifier)
}
func testProvidersWithDifferentTypeHaveDifferentIdentifier() {
let provider1 = ComponentTypeProvider(type: MockComponent.self)
let provider2 = ComponentTypeProvider(type: MockComponent2.self)
XCTAssertNotEqual(provider1.identifier, provider2.identifier)
}
class MockComponent: Component, DefaultInitializable {
var value: String
required init() {
value = ""
}
}
class MockComponent2: Component, DefaultInitializable {
var value: Bool
required init() {
value = false
}
}
}
// MARK: -
class ComponentSingletonProviderTests: XCTestCase {
func testProviderReturnsAnInstanceOfType() {
let provider = ComponentSingletonProvider(type: MockComponent.self)
let component = provider.getComponent() as? MockComponent
XCTAssertNotNil(component)
}
func testProviderReturnsSameInstanceEachTime() {
let provider = ComponentSingletonProvider(type: MockComponent.self)
let component1 = provider.getComponent() as? MockComponent
let component2 = provider.getComponent() as? MockComponent
XCTAssertTrue(component1 === component2)
}
func testProvidersWithSameTypeHaveDifferentIdentifier() {
let provider1 = ComponentSingletonProvider(type: MockComponent.self)
let provider2 = ComponentSingletonProvider(type: MockComponent.self)
XCTAssertNotEqual(provider1.identifier, provider2.identifier)
}
func testProvidersWithDifferentTypeHaveDifferentIdentifier() {
let provider1 = ComponentSingletonProvider(type: MockComponent.self)
let provider2 = ComponentSingletonProvider(type: MockComponent2.self)
XCTAssertNotEqual(provider1.identifier, provider2.identifier)
}
class MockComponent: Component, DefaultInitializable {
var value: Int
required init() {
value = 0
}
}
class MockComponent2: Component, DefaultInitializable {
var value: String
required init() {
value = ""
}
}
}
// MARK: -
class DynamicComponentProviderTests: XCTestCase {
func testProviderReturnsTheInstance() {
let instance = MockComponent(value: 0)
let providerMethod = DynamicComponentProvider.Closure { instance }
let provider = DynamicComponentProvider(closure: providerMethod)
let component = provider.getComponent() as? MockComponent
XCTAssertTrue(component === instance)
}
func testProvidersWithSameMethodHaveSameIdentifier() {
let instance = MockComponent(value: 0)
let providerMethod = DynamicComponentProvider.Closure { instance }
let provider1 = DynamicComponentProvider(closure: providerMethod)
let provider2 = DynamicComponentProvider(closure: providerMethod)
XCTAssertEqual(provider1.identifier, provider2.identifier)
}
func testProvidersWithDifferentMethodsHaveDifferentIdentifier() {
let instance = MockComponent(value: 0)
let providerMethod1 = DynamicComponentProvider.Closure { instance }
let providerMethod2 = DynamicComponentProvider.Closure { instance }
let provider1 = DynamicComponentProvider(closure: providerMethod1)
let provider2 = DynamicComponentProvider(closure: providerMethod2)
XCTAssertNotEqual(provider1.identifier, provider2.identifier)
}
class MockComponent: Component {
let value: Int
init(value: Int) {
self.value = value
}
}
}
// MARK: -
@testable import class FirebladeECS.EntityState
class EntityStateTests: XCTestCase {
private var state = EntityState()
override func setUp() {
state = EntityState()
}
override func tearDown() {
state = EntityState()
}
func testAddMappingWithNoQualifierCreatesTypeProvider() {
state.addMapping(for: MockComponent.self)
let provider = state.providers[MockComponent.identifier]
XCTAssertNotNil(provider)
XCTAssertTrue(provider is ComponentTypeProvider?)
XCTAssertTrue(provider?.getComponent() is MockComponent?)
}
func testAddMappingWithTypeQualifierCreatesTypeProvider() {
state.addMapping(for: MockComponent.self).withType(MockComponent2.self)
let provider = state.providers[MockComponent.identifier]
XCTAssertNotNil(provider)
XCTAssertTrue(provider is ComponentTypeProvider?)
XCTAssertTrue(provider?.getComponent() is MockComponent2?)
}
func testAddMappingWithInstanceQualifierCreatesInstanceProvider() {
let component = MockComponent()
state.addMapping(for: MockComponent.self).withInstance(component)
let provider = state.providers[MockComponent.identifier]
XCTAssertTrue(provider is ComponentInstanceProvider?)
XCTAssertTrue(provider?.getComponent() === component)
}
func testAddMappingWithSingletonQualifierCreatesSingletonProvider() {
state.addMapping(for: MockComponent.self).withSingleton(MockComponent.self)
let provider = state.providers[MockComponent.identifier]
XCTAssertTrue(provider is ComponentSingletonProvider?)
XCTAssertTrue(provider?.getComponent() is MockComponent?)
}
func testAddMappingWithMethodQualifierCreatesDynamicProvider() {
let dynamickProvider = DynamicComponentProvider.Closure {
MockComponent()
}
state.addMapping(for: MockComponent.self).withMethod(dynamickProvider)
let provider = state.providers[MockComponent.identifier]
XCTAssertNotNil(provider)
XCTAssertTrue(provider is DynamicComponentProvider<MockComponent>?)
XCTAssertTrue(provider?.getComponent() is MockComponent)
}
func testProviderForTypeReturnsTypeProviderByDefault() {
state.addMapping(for: MockComponent.self)
let provider = state.provider(for: MockComponent.self)
XCTAssertNotNil(provider)
XCTAssertTrue(provider is ComponentTypeProvider?)
}
func testProviderForTypeReturnsInstanceProvider() {
let component = MockComponent()
state.addMapping(for: MockComponent.self).withInstance(component)
let provider = state.provider(for: MockComponent.self)
XCTAssertNotNil(provider)
XCTAssertTrue(provider is ComponentInstanceProvider?)
}
func testProviderForTypeReturnsSingletonProvider() {
state.addMapping(for: MockComponent.self).withSingleton(MockComponent.self)
let provider = state.provider(for: MockComponent.self)
XCTAssertNotNil(provider)
XCTAssertTrue(provider is ComponentSingletonProvider?)
}
func testProviderForTypeReturnsDynamicProvider() {
state.addMapping(for: MockComponent.self).withMethod(.init { MockComponent() })
let provider = state.provider(for: MockComponent.self)
XCTAssertNotNil(provider)
XCTAssertTrue(provider is DynamicComponentProvider<MockComponent>?)
}
func testProviderForTypeReturnsTypeProvider() {
state.addMapping(for: MockComponent.self).withType(MockComponent.self)
let provider = state.provider(for: MockComponent.self)
XCTAssertNotNil(provider)
XCTAssertTrue(provider is ComponentTypeProvider?)
}
func testProviderForTypeReturnsPassedProvider() {
let singletonProvider = ComponentSingletonProvider(type: MockComponent.self)
state.addMapping(for: MockComponent.self).withProvider(singletonProvider)
let provider = state.provider(for: MockComponent.self) as? ComponentSingletonProvider
XCTAssertNotNil(provider)
XCTAssertTrue(provider === singletonProvider)
}
func testHasProviderReturnsFalseForNotCreatedProvider() {
XCTAssertFalse(state.hasProvider(for: MockComponent.self))
}
func testHasProviderReturnsTrueForCreatedProvider() {
state.addMapping(for: MockComponent.self)
XCTAssertTrue(state.hasProvider(for: MockComponent.self))
}
func testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType() {
let component = MockComponent()
state.addInstance(component)
XCTAssertTrue(state.provider(for: MockComponent.self) is ComponentInstanceProvider?)
XCTAssert(state.provider(for: MockComponent.self)?.getComponent() === component)
}
func testAddTypeCreatesMappingAndSetsTypeProviderForType() {
state.addType(MockComponent.self)
XCTAssertTrue(state.provider(for: MockComponent.self) is ComponentTypeProvider?)
XCTAssertNotNil(state.provider(for: MockComponent.self)?.getComponent())
XCTAssertTrue(state.provider(for: MockComponent.self)?.getComponent() is MockComponent?)
}
func testAddSingletonCreatesMappingAndSetsSingletonProviderForType() {
state.addSingleton(MockComponent.self)
XCTAssertTrue(state.provider(for: MockComponent.self) is ComponentSingletonProvider?)
XCTAssertNotNil(state.provider(for: MockComponent.self)?.getComponent())
XCTAssertTrue(state.provider(for: MockComponent.self)?.getComponent() is MockComponent?)
}
func testAddMethodCreatesMappingAndSetsDynamicProviderForType() {
let component = MockComponent()
state.addMethod(closure: .init { component })
XCTAssertTrue(state.provider(for: MockComponent.self) is DynamicComponentProvider<MockComponent>?)
XCTAssertTrue(state.provider(for: MockComponent.self)?.getComponent() === component)
}
func testAddProviderCreatesMappingAndSetsProvider() {
let provider = ComponentSingletonProvider(type: MockComponent.self)
state.addProvider(type: MockComponent.self, provider: provider)
XCTAssert(state.provider(for: MockComponent.self) is ComponentSingletonProvider?)
XCTAssertNotNil(state.provider(for: MockComponent.self))
}
class MockComponent: ComponentInitializable {
let value: Int
init(value: Int) {
self.value = value
}
required init() {
value = 0
}
}
class MockComponent2: MockComponent {}
}
// MARK: -
class EntityStateMachineTests: XCTestCase {
var nexus = Nexus()
var fsm = EntityStateMachine<String>(entity: .init(nexus: .init(), id: .invalid))
var entity = Entity(nexus: .init(), id: .init(rawValue: 1))
override func setUp() {
nexus = Nexus()
entity = nexus.createEntity()
fsm = EntityStateMachine(entity: entity)
}
func testEnterStateAddsStatesComponents() {
let state = EntityState()
let component = MockComponent()
state.addMapping(for: MockComponent.self).withInstance(component)
fsm.addState(name: "test", state: state)
fsm.changeState(name: "test")
XCTAssertTrue(entity.get(component: MockComponent.self) === component)
}
func testEnterSecondStateAddsSecondStatesComponents() {
let state1 = EntityState()
let component1 = MockComponent()
state1.addMapping(for: MockComponent.self).withInstance(component1)
fsm.addState(name: "test1", state: state1)
let state2 = EntityState()
let component2 = MockComponent2()
state2.addMapping(for: MockComponent2.self).withInstance(component2)
fsm.addState(name: "test2", state: state2)
fsm.changeState(name: "test2")
XCTAssertTrue(entity.get(component: MockComponent2.self) === component2)
}
func testEnterSecondStateRemovesFirstStatesComponents() {
let state1 = EntityState()
let component1 = MockComponent()
state1.addMapping(for: MockComponent.self).withInstance(component1)
fsm.addState(name: "test1", state: state1)
fsm.changeState(name: "test1")
let state2 = EntityState()
let component2 = MockComponent2()
state2.addMapping(for: MockComponent2.self).withInstance(component2)
fsm.addState(name: "test2", state: state2)
fsm.changeState(name: "test2")
XCTAssertFalse(entity.has(MockComponent.self))
}
func testEnterSecondStateDoesNotRemoveOverlappingComponents() {
class EventDelegate: NexusEventDelegate {
init() {}
func nexusEvent(_ event: NexusEvent) {
XCTAssertFalse(event is ComponentRemoved, "Component was removed when it shouldn't have been.")
}
func nexusNonFatalError(_ message: String) {}
}
let delgate = EventDelegate()
nexus.delegate = delgate
let state1 = EntityState()
let component1 = MockComponent()
state1.addMapping(for: MockComponent.self).withInstance(component1)
fsm.addState(name: "test1", state: state1)
fsm.changeState(name: "test1")
let state2 = EntityState()
let component2 = MockComponent2()
state2.addMapping(for: MockComponent.self).withInstance(component1)
state2.addMapping(for: MockComponent2.self).withInstance(component2)
fsm.addState(name: "test2", state: state2)
fsm.changeState(name: "test2")
XCTAssertTrue(entity.get(component: MockComponent.self) === component1)
}
func testEnterSecondStateRemovesDifferentComponentsOfSameType() {
let state1 = EntityState()
let component1 = MockComponent()
state1.addMapping(for: MockComponent.self).withInstance(component1)
fsm.addState(name: "test1", state: state1)
fsm.changeState(name: "test1")
let state2 = EntityState()
let component3 = MockComponent()
let component2 = MockComponent2()
state2.addMapping(for: MockComponent.self).withInstance(component3)
state2.addMapping(for: MockComponent2.self).withInstance(component2)
fsm.addState(name: "test2", state: state2)
fsm.changeState(name: "test2")
XCTAssertTrue(entity.get(component: MockComponent.self) === component3)
}
func testCreateStateAddsState() {
let state = fsm.createState(name: "test")
let component = MockComponent()
state.addMapping(for: MockComponent.self).withInstance(component)
fsm.changeState(name: "test")
XCTAssertTrue(entity.get(component: MockComponent.self) === component)
}
func testCreateStateDoesNotChangeState() {
let state = fsm.createState(name: "test")
let component = MockComponent()
state.addMapping(for: MockComponent.self).withInstance(component)
XCTAssertNil(entity.get(component: MockComponent.self))
}
func testCallChangeStateWithSameNameLeavesEntityComponentsIntact() {
let state = fsm.createState(name: "test")
let component1 = MockComponent()
let component2 = MockComponent2()
state.addMapping(for: MockComponent.self).withInstance(component1)
state.addMapping(for: MockComponent2.self).withInstance(component2)
let name = "test"
fsm.changeState(name: name)
XCTAssertTrue(entity.get(component: MockComponent.self) === component1)
XCTAssertTrue(entity.get(component: MockComponent2.self) === component2)
fsm.changeState(name: name)
XCTAssertTrue(entity.get(component: MockComponent.self) === component1)
XCTAssertTrue(entity.get(component: MockComponent2.self) === component2)
}
func testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity() {
class Marker: Component {
let fsm: EntityStateMachine<String>
init(fsm: EntityStateMachine<String>) {
self.fsm = fsm
}
}
let nexus = Nexus()
var entity = nexus.createEntity()
var markerComponent = Marker(fsm: EntityStateMachine<String>(entity: entity))
entity.assign(markerComponent)
weak var weakMarker = markerComponent
weak var weakFsm = markerComponent.fsm
nexus.destroy(entity: entity)
entity = nexus.createEntity()
markerComponent = .init(fsm: .init(entity: entity))
XCTAssertNil(weakMarker)
XCTAssertNil(weakFsm)
}
class MockComponent: ComponentInitializable {
let value: Int
init(value: Int) {
self.value = value
}
required init() {
value = 0
}
}
class MockComponent2: ComponentInitializable {
let value: String
init(value: String) {
self.value = value
}
required init() {
self.value = ""
}
}
}
// MARK: -
class StateComponentMappingTests: XCTestCase {
func testAddReturnsSameMappingForSameComponentType() {
let state = EntityState()
let mapping = state.addMapping(for: MockComponent.self)
XCTAssertFalse(mapping === mapping.add(MockComponent.self))
}
func testAddReturnsSameMappingForDifferentComponentTypes() {
let state = EntityState()
let mapping = state.addMapping(for: MockComponent.self)
XCTAssertFalse(mapping === mapping.add(MockComponent2.self))
}
func testAddAddsProviderToState() {
let state = EntityState()
let mapping = state.addMapping(for: MockComponent.self)
mapping.add(MockComponent2.self)
XCTAssertTrue(state.hasProvider(for: MockComponent.self))
}
class MockComponent: ComponentInitializable {
let value: Int
init(value: Int) {
self.value = value
}
required init() {
self.value = 0
}
}
class MockComponent2: ComponentInitializable {
let value: String
init(value: String) {
self.value = value
}
required init() {
self.value = ""
}
}
}

View File

@ -11,6 +11,29 @@ extension ComponentIdentifierTests {
]
}
extension ComponentInstanceProviderTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentInstanceProviderTests = [
("testProviderReturnsTheInstance", testProviderReturnsTheInstance),
("testProvidersWithDifferentInstanceHaveDifferentIdentifier", testProvidersWithDifferentInstanceHaveDifferentIdentifier),
("testProvidersWithSameInstanceHaveSameIdentifier", testProvidersWithSameInstanceHaveSameIdentifier)
]
}
extension ComponentSingletonProviderTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentSingletonProviderTests = [
("testProviderReturnsAnInstanceOfType", testProviderReturnsAnInstanceOfType),
("testProviderReturnsSameInstanceEachTime", testProviderReturnsSameInstanceEachTime),
("testProvidersWithDifferentTypeHaveDifferentIdentifier", testProvidersWithDifferentTypeHaveDifferentIdentifier),
("testProvidersWithSameTypeHaveDifferentIdentifier", testProvidersWithSameTypeHaveDifferentIdentifier)
]
}
extension ComponentTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
@ -20,6 +43,29 @@ extension ComponentTests {
]
}
extension ComponentTypeProviderTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentTypeProviderTests = [
("testProviderReturnsAnInstanceOfType", testProviderReturnsAnInstanceOfType),
("testProviderReturnsNewInstanceEachTime", testProviderReturnsNewInstanceEachTime),
("testProvidersWithDifferentTypeHaveDifferentIdentifier", testProvidersWithDifferentTypeHaveDifferentIdentifier),
("testProvidersWithSameTypeHaveSameIdentifier", testProvidersWithSameTypeHaveSameIdentifier)
]
}
extension DynamicComponentProviderTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__DynamicComponentProviderTests = [
("testProviderReturnsTheInstance", testProviderReturnsTheInstance),
("testProvidersWithDifferentMethodsHaveDifferentIdentifier", testProvidersWithDifferentMethodsHaveDifferentIdentifier),
("testProvidersWithSameMethodHaveSameIdentifier", testProvidersWithSameMethodHaveSameIdentifier)
]
}
extension EntityCreationTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
@ -45,6 +91,49 @@ extension EntityIdGenTests {
]
}
extension EntityStateMachineTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EntityStateMachineTests = [
("testCallChangeStateWithSameNameLeavesEntityComponentsIntact", testCallChangeStateWithSameNameLeavesEntityComponentsIntact),
("testCreateStateAddsState", testCreateStateAddsState),
("testCreateStateDoesNotChangeState", testCreateStateDoesNotChangeState),
("testEnterSecondStateAddsSecondStatesComponents", testEnterSecondStateAddsSecondStatesComponents),
("testEnterSecondStateDoesNotRemoveOverlappingComponents", testEnterSecondStateDoesNotRemoveOverlappingComponents),
("testEnterSecondStateRemovesDifferentComponentsOfSameType", testEnterSecondStateRemovesDifferentComponentsOfSameType),
("testEnterSecondStateRemovesFirstStatesComponents", testEnterSecondStateRemovesFirstStatesComponents),
("testEnterStateAddsStatesComponents", testEnterStateAddsStatesComponents),
("testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity", testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity)
]
}
extension EntityStateTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EntityStateTests = [
("testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType", testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType),
("testAddMappingWithInstanceQualifierCreatesInstanceProvider", testAddMappingWithInstanceQualifierCreatesInstanceProvider),
("testAddMappingWithMethodQualifierCreatesDynamicProvider", testAddMappingWithMethodQualifierCreatesDynamicProvider),
("testAddMappingWithNoQualifierCreatesTypeProvider", testAddMappingWithNoQualifierCreatesTypeProvider),
("testAddMappingWithSingletonQualifierCreatesSingletonProvider", testAddMappingWithSingletonQualifierCreatesSingletonProvider),
("testAddMappingWithTypeQualifierCreatesTypeProvider", testAddMappingWithTypeQualifierCreatesTypeProvider),
("testAddMethodCreatesMappingAndSetsDynamicProviderForType", testAddMethodCreatesMappingAndSetsDynamicProviderForType),
("testAddProviderCreatesMappingAndSetsProvider", testAddProviderCreatesMappingAndSetsProvider),
("testAddSingletonCreatesMappingAndSetsSingletonProviderForType", testAddSingletonCreatesMappingAndSetsSingletonProviderForType),
("testAddTypeCreatesMappingAndSetsTypeProviderForType", testAddTypeCreatesMappingAndSetsTypeProviderForType),
("testHasProviderReturnsFalseForNotCreatedProvider", testHasProviderReturnsFalseForNotCreatedProvider),
("testHasProviderReturnsTrueForCreatedProvider", testHasProviderReturnsTrueForCreatedProvider),
("testProviderForTypeReturnsDynamicProvider", testProviderForTypeReturnsDynamicProvider),
("testProviderForTypeReturnsInstanceProvider", testProviderForTypeReturnsInstanceProvider),
("testProviderForTypeReturnsPassedProvider", testProviderForTypeReturnsPassedProvider),
("testProviderForTypeReturnsSingletonProvider", testProviderForTypeReturnsSingletonProvider),
("testProviderForTypeReturnsTypeProvider", testProviderForTypeReturnsTypeProvider),
("testProviderForTypeReturnsTypeProviderByDefault", testProviderForTypeReturnsTypeProviderByDefault)
]
}
extension EntityTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
@ -291,6 +380,17 @@ extension SparseSetTests {
]
}
extension StateComponentMappingTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__StateComponentMappingTests = [
("testAddAddsProviderToState", testAddAddsProviderToState),
("testAddReturnsSameMappingForDifferentComponentTypes", testAddReturnsSameMappingForDifferentComponentTypes),
("testAddReturnsSameMappingForSameComponentType", testAddReturnsSameMappingForSameComponentType)
]
}
extension SystemsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
@ -303,9 +403,15 @@ extension SystemsTests {
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
testCase(ComponentInstanceProviderTests.__allTests__ComponentInstanceProviderTests),
testCase(ComponentSingletonProviderTests.__allTests__ComponentSingletonProviderTests),
testCase(ComponentTests.__allTests__ComponentTests),
testCase(ComponentTypeProviderTests.__allTests__ComponentTypeProviderTests),
testCase(DynamicComponentProviderTests.__allTests__DynamicComponentProviderTests),
testCase(EntityCreationTests.__allTests__EntityCreationTests),
testCase(EntityIdGenTests.__allTests__EntityIdGenTests),
testCase(EntityStateMachineTests.__allTests__EntityStateMachineTests),
testCase(EntityStateTests.__allTests__EntityStateTests),
testCase(EntityTests.__allTests__EntityTests),
testCase(Family1Tests.__allTests__Family1Tests),
testCase(Family2Tests.__allTests__Family2Tests),
@ -322,6 +428,7 @@ public func __allTests() -> [XCTestCaseEntry] {
testCase(NexusTests.__allTests__NexusTests),
testCase(SingleTests.__allTests__SingleTests),
testCase(SparseSetTests.__allTests__SparseSetTests),
testCase(StateComponentMappingTests.__allTests__StateComponentMappingTests),
testCase(SystemsTests.__allTests__SystemsTests)
]
}