diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index bc75c19..9a8de0d 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -1,10 +1,17 @@ -/// Requires initializer with no arguments. -/// In case of component - makes sure it can be instantiated by component provider -public protocol EmptyInitializable { +// +// 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 & EmptyInitializable +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, @@ -20,7 +27,7 @@ public protocol ComponentProvider { /// 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 confirms to Hashable protocol + /// - Returns: struct/class instance that conforms to Hashable protocol var identifier: AnyHashable { get } /// Used to request a component from the provider. @@ -32,7 +39,7 @@ public protocol ComponentProvider { /// This component provider always returns the same instance of the component. The instance /// is passed to the provider at initialisation. -public class ComponentInstanceProvider { +public final class ComponentInstanceProvider { private var instance: Component /// Initializer @@ -61,7 +68,7 @@ extension ComponentInstanceProvider: ComponentProvider { /// 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 class ComponentTypeProvider { +public final class ComponentTypeProvider { private var componentType: ComponentInitializable.Type /// Used to compare this provider with others. Any ComponentTypeProvider that returns @@ -89,7 +96,7 @@ extension ComponentTypeProvider: ComponentProvider { /// 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 class ComponentSingletonProvider { +public final class ComponentSingletonProvider { private lazy var instance: Component = { componentType.init() }() @@ -122,9 +129,9 @@ extension ComponentSingletonProvider: ComponentProvider { /// This component provider calls a function to get the component instance. The function must /// return a single component of the appropriate type. -public class DynamicComponentProvider { +public final class DynamicComponentProvider { /// Wrapper for closure to make it hashable via ObjectIdentifier - public class Closure { + public final class Closure { let provideComponent: () -> C /// Initializer @@ -168,26 +175,26 @@ public class EntityState { public init() {} - /// Add a new ComponentMapping to this state. The mapping is a utility class that is used to + /// 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 add(_ type: ComponentInitializable.Type) -> StateComponentMapping { + 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 get(_ type: ComponentInitializable.Type) -> 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 has(_ type: ComponentInitializable.Type) -> Bool { + public func hasProvider(for type: ComponentInitializable.Type) -> Bool { providers[type.identifier] != nil } } @@ -202,7 +209,7 @@ extension EntityState { @discardableResult @inline(__always) public func addInstance(_ component: C) -> Self { - add(C.self).withInstance(component) + addMapping(for: C.self).withInstance(component) return self } @@ -213,7 +220,7 @@ extension EntityState { @inline(__always) @discardableResult public func addType(_ type: ComponentInitializable.Type) -> Self { - add(type).withType(type) + addMapping(for: type).withType(type) return self } @@ -225,7 +232,7 @@ extension EntityState { @inline(__always) @discardableResult public func addSingleton(_ type: ComponentInitializable.Type) -> Self { - add(type).withSingleton(type) + addMapping(for: type).withSingleton(type) return self } @@ -236,7 +243,7 @@ extension EntityState { @inline(__always) @discardableResult public func addMethod(closure: DynamicComponentProvider.Closure) -> Self { - add(C.self).withMethod(closure) + addMapping(for: C.self).withMethod(closure) return self } @@ -247,7 +254,7 @@ extension EntityState { @inline(__always) @discardableResult public func addProvider(type: C.Type, provider: ComponentProvider) -> Self { - add(type).withProvider(provider) + addMapping(for: type).withProvider(provider) return self } } @@ -325,13 +332,13 @@ public class StateComponentMapping { return self } - /// Maps through to the add method of the EntityState that this mapping belongs to + /// 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.add(type) + creatingState.addMapping(for: type) } private func setProvider(_ provider: ComponentProvider) { @@ -345,9 +352,9 @@ public class StateComponentMapping { /// 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 StateName: Generic hashable state name type -public class EntityStateMachine { - private var states: [StateName: EntityState] +/// - Parameter StateIdentifier: Generic hashable state name type +public class EntityStateMachine { + private var states: [StateIdentifier: EntityState] /// The current state of the state machine. private var currentState: EntityState? @@ -366,7 +373,7 @@ public class EntityStateMachine { /// - Parameter state: The state. /// - Returns: This state machine, so methods can be chained. @discardableResult - public func addState(name: StateName, state: EntityState) -> Self { + public func addState(name: StateIdentifier, state: EntityState) -> Self { states[name] = state return self } @@ -375,7 +382,7 @@ public class EntityStateMachine { /// - 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: StateName) -> EntityState { + public func createState(name: StateIdentifier) -> EntityState { let state = EntityState() states[name] = state return state @@ -384,7 +391,7 @@ public class EntityStateMachine { /// 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: StateName) { + public func changeState(name: StateIdentifier) { guard let newState = states[name] else { assertionFailure("Entity state '\(name)' doesn't exist") return diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 28fa552..e61e351 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -1,3 +1,10 @@ +// +// FSMTests.swift +// FirebladeECSTests +// +// Created by Igor Kravchenko on 29.09.2020. +// + import FirebladeECS import XCTest @@ -59,19 +66,19 @@ class ComponentTypeProviderTests: XCTestCase { XCTAssertNotEqual(provider1.identifier, provider2.identifier) } - class MockComponent: Component, EmptyInitializable { + class MockComponent: Component, DefaultInitializable { var value: String required init() { - self.value = "" + value = "" } } - class MockComponent2: Component, EmptyInitializable { + class MockComponent2: Component, DefaultInitializable { var value: Bool required init() { - self.value = false + value = false } } } @@ -105,19 +112,19 @@ class ComponentSingletonProviderTests: XCTestCase { XCTAssertNotEqual(provider1.identifier, provider2.identifier) } - class MockComponent: Component, EmptyInitializable { + class MockComponent: Component, DefaultInitializable { var value: Int required init() { - self.value = 0 + value = 0 } } - class MockComponent2: Component, EmptyInitializable { + class MockComponent2: Component, DefaultInitializable { var value: String required init() { - self.value = "" + value = "" } } } @@ -174,135 +181,135 @@ class EntityStateTests: XCTestCase { state = EntityState() } - func testAddWithNoQualifierCreatesTypeProvider() { - state.add(MockComponent.self) + 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 testAddWithTypeQualifierCreatesTypeProvider() { - state.add(MockComponent.self).withType(MockComponent2.self) + 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 testAddWithInstanceQualifierCreatesInstanceProvider() { + func testAddMappingWithInstanceQualifierCreatesInstanceProvider() { let component = MockComponent() - state.add(MockComponent.self).withInstance(component) + state.addMapping(for: MockComponent.self).withInstance(component) let provider = state.providers[MockComponent.identifier] XCTAssertTrue(provider is ComponentInstanceProvider?) XCTAssertTrue(provider?.getComponent() === component) } - func testAddWithSingletonQualifierCreatesSingletonProvider() { - state.add(MockComponent.self).withSingleton(MockComponent.self) + 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 testAddWithMethodQualifierCreatesDynamicProvider() { + func testAddMappingWithMethodQualifierCreatesDynamicProvider() { let dynamickProvider = DynamicComponentProvider.Closure { MockComponent() } - state.add(MockComponent.self).withMethod(dynamickProvider) + state.addMapping(for: MockComponent.self).withMethod(dynamickProvider) let provider = state.providers[MockComponent.identifier] XCTAssertNotNil(provider) XCTAssertTrue(provider is DynamicComponentProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent) } - func testGetReturnsTypeProviderByDefault() { - state.add(MockComponent.self) - let provider = state.get(MockComponent.self) + func testProviderForTypeReturnsTypeProviderByDefault() { + state.addMapping(for: MockComponent.self) + let provider = state.provider(for: MockComponent.self) XCTAssertNotNil(provider) XCTAssertTrue(provider is ComponentTypeProvider?) } - func testGetReturnsInstanceProvider() { + func testProviderForTypeReturnsInstanceProvider() { let component = MockComponent() - state.add(MockComponent.self).withInstance(component) - let provider = state.get(MockComponent.self) + state.addMapping(for: MockComponent.self).withInstance(component) + let provider = state.provider(for: MockComponent.self) XCTAssertNotNil(provider) XCTAssertTrue(provider is ComponentInstanceProvider?) } - func testGetReturnsSingletonProvider() { - state.add(MockComponent.self).withSingleton(MockComponent.self) - let provider = state.get(MockComponent.self) + func testProviderForTypeReturnsSingletonProvider() { + state.addMapping(for: MockComponent.self).withSingleton(MockComponent.self) + let provider = state.provider(for: MockComponent.self) XCTAssertNotNil(provider) XCTAssertTrue(provider is ComponentSingletonProvider?) } - func testGetReturnsDynamicProvider() { - state.add(MockComponent.self).withMethod(.init { MockComponent() }) - let provider = state.get(MockComponent.self) + func testProviderForTypeReturnsDynamicProvider() { + state.addMapping(for: MockComponent.self).withMethod(.init { MockComponent() }) + let provider = state.provider(for: MockComponent.self) XCTAssertNotNil(provider) XCTAssertTrue(provider is DynamicComponentProvider?) } - func testGetReturnsTypeProvider() { - state.add(MockComponent.self).withType(MockComponent.self) - let provider = state.get(MockComponent.self) + func testProviderForTypeReturnsTypeProvider() { + state.addMapping(for: MockComponent.self).withType(MockComponent.self) + let provider = state.provider(for: MockComponent.self) XCTAssertNotNil(provider) XCTAssertTrue(provider is ComponentTypeProvider?) } - func testGetReturnsPassedProvider() { + func testProviderForTypeReturnsPassedProvider() { let singletonProvider = ComponentSingletonProvider(type: MockComponent.self) - state.add(MockComponent.self).withProvider(singletonProvider) - let provider = state.get(MockComponent.self) as? ComponentSingletonProvider + state.addMapping(for: MockComponent.self).withProvider(singletonProvider) + let provider = state.provider(for: MockComponent.self) as? ComponentSingletonProvider XCTAssertNotNil(provider) XCTAssertTrue(provider === singletonProvider) } - func testHasReturnsFalseForNotCreatedProvider() { - XCTAssertFalse(state.has(MockComponent.self)) + func testHasProviderReturnsFalseForNotCreatedProvider() { + XCTAssertFalse(state.hasProvider(for: MockComponent.self)) } - func testHasReturnsTrueForCreatedProvider() { - state.add(MockComponent.self) - XCTAssertTrue(state.has(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.get(MockComponent.self) is ComponentInstanceProvider?) - XCTAssert(state.get(MockComponent.self)?.getComponent() === 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.get(MockComponent.self) is ComponentTypeProvider?) - XCTAssertNotNil(state.get(MockComponent.self)?.getComponent()) - XCTAssertTrue(state.get(MockComponent.self)?.getComponent() is MockComponent?) + 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.get(MockComponent.self) is ComponentSingletonProvider?) - XCTAssertNotNil(state.get(MockComponent.self)?.getComponent()) - XCTAssertTrue(state.get(MockComponent.self)?.getComponent() is MockComponent?) + 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.get(MockComponent.self) is DynamicComponentProvider?) - XCTAssertTrue(state.get(MockComponent.self)?.getComponent() === component) + XCTAssertTrue(state.provider(for: MockComponent.self) is DynamicComponentProvider?) + 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.get(MockComponent.self) is ComponentSingletonProvider?) - XCTAssertNotNil(state.get(MockComponent.self)) + XCTAssert(state.provider(for: MockComponent.self) is ComponentSingletonProvider?) + XCTAssertNotNil(state.provider(for: MockComponent.self)) } class MockComponent: ComponentInitializable { @@ -313,7 +320,7 @@ class EntityStateTests: XCTestCase { } required init() { - self.value = 0 + value = 0 } } @@ -336,7 +343,7 @@ class EntityStateMachineTests: XCTestCase { func testEnterStateAddsStatesComponents() { let state = EntityState() let component = MockComponent() - state.add(MockComponent.self).withInstance(component) + state.addMapping(for: MockComponent.self).withInstance(component) fsm.addState(name: "test", state: state) fsm.changeState(name: "test") XCTAssertTrue(entity.get(component: MockComponent.self) === component) @@ -345,12 +352,12 @@ class EntityStateMachineTests: XCTestCase { func testEnterSecondStateAddsSecondStatesComponents() { let state1 = EntityState() let component1 = MockComponent() - state1.add(MockComponent.self).withInstance(component1) + state1.addMapping(for: MockComponent.self).withInstance(component1) fsm.addState(name: "test1", state: state1) let state2 = EntityState() let component2 = MockComponent2() - state2.add(MockComponent2.self).withInstance(component2) + state2.addMapping(for: MockComponent2.self).withInstance(component2) fsm.addState(name: "test2", state: state2) fsm.changeState(name: "test2") @@ -360,13 +367,13 @@ class EntityStateMachineTests: XCTestCase { func testEnterSecondStateRemovesFirstStatesComponents() { let state1 = EntityState() let component1 = MockComponent() - state1.add(MockComponent.self).withInstance(component1) + state1.addMapping(for: MockComponent.self).withInstance(component1) fsm.addState(name: "test1", state: state1) fsm.changeState(name: "test1") let state2 = EntityState() let component2 = MockComponent2() - state2.add(MockComponent2.self).withInstance(component2) + state2.addMapping(for: MockComponent2.self).withInstance(component2) fsm.addState(name: "test2", state: state2) fsm.changeState(name: "test2") @@ -387,14 +394,14 @@ class EntityStateMachineTests: XCTestCase { nexus.delegate = delgate let state1 = EntityState() let component1 = MockComponent() - state1.add(MockComponent.self).withInstance(component1) + state1.addMapping(for: MockComponent.self).withInstance(component1) fsm.addState(name: "test1", state: state1) fsm.changeState(name: "test1") let state2 = EntityState() let component2 = MockComponent2() - state2.add(MockComponent.self).withInstance(component1) - state2.add(MockComponent2.self).withInstance(component2) + state2.addMapping(for: MockComponent.self).withInstance(component1) + state2.addMapping(for: MockComponent2.self).withInstance(component2) fsm.addState(name: "test2", state: state2) fsm.changeState(name: "test2") @@ -404,15 +411,15 @@ class EntityStateMachineTests: XCTestCase { func testEnterSecondStateRemovesDifferentComponentsOfSameType() { let state1 = EntityState() let component1 = MockComponent() - state1.add(MockComponent.self).withInstance(component1) + 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.add(MockComponent.self).withInstance(component3) - state2.add(MockComponent2.self).withInstance(component2) + state2.addMapping(for: MockComponent.self).withInstance(component3) + state2.addMapping(for: MockComponent2.self).withInstance(component2) fsm.addState(name: "test2", state: state2) fsm.changeState(name: "test2") @@ -422,7 +429,7 @@ class EntityStateMachineTests: XCTestCase { func testCreateStateAddsState() { let state = fsm.createState(name: "test") let component = MockComponent() - state.add(MockComponent.self).withInstance(component) + state.addMapping(for: MockComponent.self).withInstance(component) fsm.changeState(name: "test") XCTAssertTrue(entity.get(component: MockComponent.self) === component) } @@ -430,7 +437,7 @@ class EntityStateMachineTests: XCTestCase { func testCreateStateDoesNotChangeState() { let state = fsm.createState(name: "test") let component = MockComponent() - state.add(MockComponent.self).withInstance(component) + state.addMapping(for: MockComponent.self).withInstance(component) XCTAssertNil(entity.get(component: MockComponent.self)) } @@ -438,8 +445,8 @@ class EntityStateMachineTests: XCTestCase { let state = fsm.createState(name: "test") let component1 = MockComponent() let component2 = MockComponent2() - state.add(MockComponent.self).withInstance(component1) - state.add(MockComponent2.self).withInstance(component2) + 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) @@ -478,7 +485,7 @@ class EntityStateMachineTests: XCTestCase { } required init() { - self.value = 0 + value = 0 } } @@ -500,21 +507,21 @@ class EntityStateMachineTests: XCTestCase { class StateComponentMappingTests: XCTestCase { func testAddReturnsSameMappingForSameComponentType() { let state = EntityState() - let mapping = state.add(MockComponent.self) + let mapping = state.addMapping(for: MockComponent.self) XCTAssertFalse(mapping === mapping.add(MockComponent.self)) } func testAddReturnsSameMappingForDifferentComponentTypes() { let state = EntityState() - let mapping = state.add(MockComponent.self) + let mapping = state.addMapping(for: MockComponent.self) XCTAssertFalse(mapping === mapping.add(MockComponent2.self)) } func testAddAddsProviderToState() { let state = EntityState() - let mapping = state.add(MockComponent.self) + let mapping = state.addMapping(for: MockComponent.self) mapping.add(MockComponent2.self) - XCTAssertTrue(state.has(MockComponent.self)) + XCTAssertTrue(state.hasProvider(for: MockComponent.self)) } class MockComponent: ComponentInitializable {