From acc2a0feff8bfc08f847b85a909fbb872b6e3d8a Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 17 Sep 2020 20:17:52 +0300 Subject: [PATCH 01/26] Add initial implementation of component providers for entity FSM --- Sources/FirebladeECS/FSM.swift | 167 +++++++++++++++++++++++++ Tests/FirebladeECSTests/FSMTests.swift | 155 +++++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 Sources/FirebladeECS/FSM.swift create mode 100644 Tests/FirebladeECSTests/FSMTests.swift diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift new file mode 100644 index 0000000..c1ea41b --- /dev/null +++ b/Sources/FirebladeECS/FSM.swift @@ -0,0 +1,167 @@ +public protocol EmptyInitializable { + init() +} + +public typealias ComponentInitializable = Component & EmptyInitializable + +public protocol ComponentProvider { + var identifier: AnyHashable { get } + func getComponent() -> C? where C: Component +} + +// MARK: - + +public struct ComponentInstanceProvider { + private var instance: Component + + public init(instance: Component) { + self.instance = instance + } +} + +extension ComponentInstanceProvider: ComponentProvider { + public var identifier: AnyHashable { + ObjectIdentifier(instance) + } + + public func getComponent() -> C? where C : Component { + instance as? C + } +} + +// MARK: - + +public struct ComponentTypeProvider { + private var componentType: ComponentInitializable.Type + public let identifier: AnyHashable + + public init(type: T.Type) { + componentType = type + identifier = ObjectIdentifier(componentType.self) + } +} + +extension ComponentTypeProvider: ComponentProvider { + public func getComponent() -> C? where C: Component { + componentType.init() as? C + } +} + + +// MARK: - + +public class ComponentSingletonProvider { + lazy private var instance: Component = { + componentType.init() + }() + private var componentType: ComponentInitializable.Type + + public var identifier: AnyHashable { + ObjectIdentifier(instance) + } + + public init(type: T.Type) { + componentType = type + } + + internal init(type: ComponentInitializable.Type) { + componentType = type + } +} + +extension ComponentSingletonProvider: ComponentProvider { + public func getComponent() -> C? where C: Component { + instance as? C + } +} + +// MARK: - + +public struct DynamicComponentProvider { + public class Closure { + let closure: () -> Component + public init(closure: @escaping () -> Component) { + self.closure = closure + } + } + private let closure: Closure + + public init(closure: Closure) { + self.closure = closure + } +} + +extension DynamicComponentProvider: ComponentProvider { + public var identifier: AnyHashable { + ObjectIdentifier(closure) + } + + public func getComponent() -> C? where C: Component { + closure.closure() as? C + } +} + +// MARK: - + +public class EntityState { + internal var providers = [ComponentIdentifier: ComponentProvider]() + + public init() { } + + public func add(_ type: C.Type) -> StateComponentMapping { + StateComponentMapping(creatingState: self, + type: type) + } +} +// MARK: - + +public class StateComponentMapping { + private var componentType: ComponentInitializable.Type + private let creatingState: EntityState + private var provider: ComponentProvider + + public init(creatingState: EntityState, type: T.Type) { + self.creatingState = creatingState + componentType = type + provider = ComponentTypeProvider(type: type) + } + + public func withInstance(_ component: Component) -> StateComponentMapping { + setProvider(ComponentInstanceProvider(instance: component)) + return self + } + + public func withType(_ type: T.Type) -> Self { + setProvider(ComponentTypeProvider(type: type)) + return self + } + + public func withSingleton(_ type: T.Type?) -> Self { + if let type = type { + setProvider(ComponentSingletonProvider(type: type)) + } else { + setProvider(ComponentSingletonProvider(type: componentType)) + } + + return self + } + + public func withMethod(_ closure: DynamicComponentProvider.Closure) -> Self { + setProvider(DynamicComponentProvider(closure: closure)) + return self + } + + public func withProvider(_ provider: ComponentProvider) -> Self { + setProvider(provider) + return self + } + + public func add(_ type: T.Type) -> StateComponentMapping { + creatingState.add(type) + } + + private func setProvider(_ provider: ComponentProvider) { + self.provider = provider + creatingState.providers[componentType.identifier] = provider + } +} diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift new file mode 100644 index 0000000..aeeda68 --- /dev/null +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -0,0 +1,155 @@ +import FirebladeECS +import XCTest + +class ComponentInstanceProviderTests: XCTestCase { + func testProviderReturnsTheInstance() { + let instance = MockComponent(value: .max) + let provider1 = ComponentInstanceProvider(instance: instance) + let providedComponent: MockComponent? = provider1.getComponent() + 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 + } + } +} + +class ComponentTypeProviderTests: XCTestCase { + func testProviderReturnsAnInstanceOfType() { + let provider = ComponentTypeProvider(type: MockComponent.self) + let component: MockComponent? = provider.getComponent() + XCTAssertNotNil(component) + } + + func testProviderReturnsNewInstanceEachTime() { + let provider = ComponentTypeProvider(type: MockComponent.self) + let component1: MockComponent? = provider.getComponent() + let component2: MockComponent? = provider.getComponent() + 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, EmptyInitializable { + var value: String + + required init() { + self.value = "" + } + } + + class MockComponent2: Component, EmptyInitializable { + var value: Bool + + required init() { + self.value = false + } + } + +} + +class ComponentSingletonProviderTests: XCTestCase { + func testProviderReturnsAnInstanceOfType() { + let provider = ComponentSingletonProvider(type: MockComponent.self) + let component: MockComponent? = provider.getComponent() + XCTAssertNotNil(component) + } + + func testProviderReturnsSameInstanceEachTime() { + let provider = ComponentSingletonProvider(type: MockComponent.self) + let component1: MockComponent? = provider.getComponent() + let component2: MockComponent? = provider.getComponent() + 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, EmptyInitializable { + var value: Int + + required init() { + self.value = 0 + } + } + + class MockComponent2: Component, EmptyInitializable { + var value: String + + required init() { + self.value = "" + } + } +} + +class DynamicComponentProviderTests: XCTestCase { + func testProviderReturnsTheInstance() { + let instance = MockComponent(value: 0) + let providerMethod = DynamicComponentProvider.Closure { instance } + let provider = DynamicComponentProvider(closure: providerMethod) + let component: MockComponent? = provider.getComponent() + 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 + } + } +} From a17fbea014e60e5a852897cc3be9ff9b7ea5b7df Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Mon, 28 Sep 2020 00:33:20 +0300 Subject: [PATCH 02/26] add EntityStateMachine --- Sources/FirebladeECS/FSM.swift | 92 +++++++++++++++++++++++--- Tests/FirebladeECSTests/FSMTests.swift | 20 +++--- 2 files changed, 93 insertions(+), 19 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index c1ea41b..946f5c2 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -6,7 +6,7 @@ public typealias ComponentInitializable = Component & EmptyInitializable public protocol ComponentProvider { var identifier: AnyHashable { get } - func getComponent() -> C? where C: Component + func getComponent() -> Component } // MARK: - @@ -24,8 +24,8 @@ extension ComponentInstanceProvider: ComponentProvider { ObjectIdentifier(instance) } - public func getComponent() -> C? where C : Component { - instance as? C + public func getComponent() -> Component { + instance } } @@ -42,8 +42,8 @@ public struct ComponentTypeProvider { } extension ComponentTypeProvider: ComponentProvider { - public func getComponent() -> C? where C: Component { - componentType.init() as? C + public func getComponent() -> Component { + componentType.init() } } @@ -70,8 +70,8 @@ public class ComponentSingletonProvider { } extension ComponentSingletonProvider: ComponentProvider { - public func getComponent() -> C? where C: Component { - instance as? C + public func getComponent() -> Component { + instance } } @@ -96,8 +96,8 @@ extension DynamicComponentProvider: ComponentProvider { ObjectIdentifier(closure) } - public func getComponent() -> C? where C: Component { - closure.closure() as? C + public func getComponent() -> Component { + closure.closure() } } @@ -109,8 +109,15 @@ public class EntityState { public init() { } public func add(_ type: C.Type) -> StateComponentMapping { - StateComponentMapping(creatingState: self, - type: type) + StateComponentMapping(creatingState: self, type: type) + } + + public func get(_ type: C.Type) -> ComponentProvider? { + providers[type.identifier] + } + + public func has(_ type: C.Type) -> Bool { + providers[type.identifier] != nil } } // MARK: - @@ -165,3 +172,66 @@ public class StateComponentMapping { creatingState.providers[componentType.identifier] = provider } } + +// MARK: - + +public class EntityStateMachine { + private var states: [String: EntityState] + + private var currentState: EntityState? + + public var entity: Entity + + public init(entity: Entity) { + self.entity = entity + states = [:] + } + + public func addState(name: String, state: EntityState) -> Self { + states[name] = state + return self + } + + public func createState(name: String) -> EntityState { + let state = EntityState() + states[name] = state + return state + } + + public func changeState(name: String) { + guard let newState = states[name] else { + fatalError("Entity state '\(name)' doesn't exist") + } + + if newState === currentState { + return + } + var toAdd: [ComponentIdentifier: ComponentProvider] + if let currentState = currentState { + toAdd = .init() + for t in newState.providers { + toAdd[t.key] = t.value + } + + for t in currentState.providers { + if let other = toAdd[t.key], let current = currentState.providers[t.key], + current.identifier == other.identifier { + toAdd[t.key] = nil + } else { + entity.remove(t.key) + } + + } + } else { + toAdd = newState.providers + } + + for t in toAdd { + guard let component = toAdd[t.key]?.getComponent() else { + continue + } + entity.assign(component) + } + currentState = newState + } +} diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index aeeda68..bfd3833 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -5,7 +5,7 @@ class ComponentInstanceProviderTests: XCTestCase { func testProviderReturnsTheInstance() { let instance = MockComponent(value: .max) let provider1 = ComponentInstanceProvider(instance: instance) - let providedComponent: MockComponent? = provider1.getComponent() + let providedComponent = provider1.getComponent() as? MockComponent XCTAssertTrue(providedComponent === instance) } @@ -34,14 +34,14 @@ class ComponentInstanceProviderTests: XCTestCase { class ComponentTypeProviderTests: XCTestCase { func testProviderReturnsAnInstanceOfType() { let provider = ComponentTypeProvider(type: MockComponent.self) - let component: MockComponent? = provider.getComponent() + let component = provider.getComponent() as? MockComponent XCTAssertNotNil(component) } func testProviderReturnsNewInstanceEachTime() { let provider = ComponentTypeProvider(type: MockComponent.self) - let component1: MockComponent? = provider.getComponent() - let component2: MockComponent? = provider.getComponent() + let component1 = provider.getComponent() as? MockComponent + let component2 = provider.getComponent() as? MockComponent XCTAssertFalse(component1 === component2) } @@ -78,14 +78,14 @@ class ComponentTypeProviderTests: XCTestCase { class ComponentSingletonProviderTests: XCTestCase { func testProviderReturnsAnInstanceOfType() { let provider = ComponentSingletonProvider(type: MockComponent.self) - let component: MockComponent? = provider.getComponent() + let component = provider.getComponent() as? MockComponent XCTAssertNotNil(component) } func testProviderReturnsSameInstanceEachTime() { let provider = ComponentSingletonProvider(type: MockComponent.self) - let component1: MockComponent? = provider.getComponent() - let component2: MockComponent? = provider.getComponent() + let component1 = provider.getComponent() as? MockComponent + let component2 = provider.getComponent() as? MockComponent XCTAssertTrue(component1 === component2) } @@ -124,7 +124,7 @@ class DynamicComponentProviderTests: XCTestCase { let instance = MockComponent(value: 0) let providerMethod = DynamicComponentProvider.Closure { instance } let provider = DynamicComponentProvider(closure: providerMethod) - let component: MockComponent? = provider.getComponent() + let component = provider.getComponent() as? MockComponent XCTAssertTrue(component === instance) } @@ -153,3 +153,7 @@ class DynamicComponentProviderTests: XCTestCase { } } } + +class EntityStateMachineTests: XCTestCase { + // TODO: +} From 4f42a69d82b0e4b8339bb12a7aa93a6797d34edd Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Mon, 28 Sep 2020 23:35:57 +0300 Subject: [PATCH 03/26] add EntityStateTests --- Sources/FirebladeECS/FSM.swift | 26 +++------- Tests/FirebladeECSTests/FSMTests.swift | 68 ++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 946f5c2..d11567e 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -60,11 +60,7 @@ public class ComponentSingletonProvider { ObjectIdentifier(instance) } - public init(type: T.Type) { - componentType = type - } - - internal init(type: ComponentInitializable.Type) { + public init(type: ComponentInitializable.Type) { componentType = type } } @@ -108,7 +104,7 @@ public class EntityState { public init() { } - public func add(_ type: C.Type) -> StateComponentMapping { + @discardableResult public func add(_ type: C.Type) -> StateComponentMapping { StateComponentMapping(creatingState: self, type: type) } @@ -133,32 +129,27 @@ public class StateComponentMapping { provider = ComponentTypeProvider(type: type) } - public func withInstance(_ component: Component) -> StateComponentMapping { + @discardableResult public func withInstance(_ component: Component) -> StateComponentMapping { setProvider(ComponentInstanceProvider(instance: component)) return self } - public func withType(_ type: T.Type) -> Self { + @discardableResult public func withType(_ type: T.Type) -> Self { setProvider(ComponentTypeProvider(type: type)) return self } - public func withSingleton(_ type: T.Type?) -> Self { - if let type = type { - setProvider(ComponentSingletonProvider(type: type)) - } else { - setProvider(ComponentSingletonProvider(type: componentType)) - } - + @discardableResult public func withSingleton(_ type: T.Type?) -> Self { + setProvider(ComponentSingletonProvider(type: type ?? componentType)) return self } - public func withMethod(_ closure: DynamicComponentProvider.Closure) -> Self { + @discardableResult public func withMethod(_ closure: DynamicComponentProvider.Closure) -> Self { setProvider(DynamicComponentProvider(closure: closure)) return self } - public func withProvider(_ provider: ComponentProvider) -> Self { + @discardableResult public func withProvider(_ provider: ComponentProvider) -> Self { setProvider(provider) return self } @@ -220,7 +211,6 @@ public class EntityStateMachine { } else { entity.remove(t.key) } - } } else { toAdd = newState.providers diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index bfd3833..4402448 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -154,6 +154,74 @@ class DynamicComponentProviderTests: XCTestCase { } } +@testable import class FirebladeECS.EntityState + +class EntityStateTests: XCTestCase { + private var state = EntityState() + + override func setUp() { + state = EntityState() + } + + override func tearDown() { + state = EntityState() + } + + func testAddWithNoQualifierCreatesTypeProvider() { + state.add(MockComponent.self) + let provider = state.providers[MockComponent.identifier] + XCTAssertTrue(provider is ComponentTypeProvider?) + XCTAssertTrue(provider?.getComponent() is MockComponent?) + } + + func testAddWithTypeQualifierCreatesTypeProvider() { + state.add(MockComponent.self).withType(MockComponent2.self) + let provider = state.providers[MockComponent.identifier] + XCTAssertTrue(provider is ComponentTypeProvider?) + XCTAssertTrue(provider?.getComponent() is MockComponent2?) + } + + func testAddWithInstanceQualifierCreatesInstanceProvider() { + let component = MockComponent() + state.add(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) + let provider = state.providers[MockComponent.identifier] + XCTAssertTrue(provider is ComponentSingletonProvider?) + XCTAssertTrue(provider?.getComponent() is MockComponent?) + } + + func testAddWithMethodQualifierCreatesDynamicProvider() { + let dynamickProvider = DynamicComponentProvider.Closure { + MockComponent() + } + + state.add(MockComponent.self).withMethod(dynamickProvider) + let provider = state.providers[MockComponent.identifier] + XCTAssertTrue(provider is DynamicComponentProvider?) + XCTAssertTrue(provider?.getComponent() is MockComponent) + } + + class MockComponent: ComponentInitializable { + let value: Int + + init(value: Int) { + self.value = value + } + + required init() { + self.value = 0 + } + } + + class MockComponent2: MockComponent {} +} + class EntityStateMachineTests: XCTestCase { // TODO: } From 08e20d1656489b43ec47bb8973608511417e5085 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Tue, 29 Sep 2020 12:11:43 +0300 Subject: [PATCH 04/26] add EntityStateMachineTests --- Sources/FirebladeECS/FSM.swift | 2 +- Tests/FirebladeECSTests/FSMTests.swift | 110 ++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index d11567e..1412f71 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -178,7 +178,7 @@ public class EntityStateMachine { states = [:] } - public func addState(name: String, state: EntityState) -> Self { + @discardableResult public func addState(name: String, state: EntityState) -> Self { states[name] = state return self } diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 4402448..64fc208 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -223,5 +223,113 @@ class EntityStateTests: XCTestCase { } class EntityStateMachineTests: XCTestCase { - // TODO: + + var nexus = Nexus() + var fsm = EntityStateMachine(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.add(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.add(MockComponent.self).withInstance(component1) + fsm.addState(name: "test1", state: state1) + + let state2 = EntityState() + let component2 = MockComponent2() + state2.add(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.add(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) + fsm.addState(name: "test2", state: state2) + fsm.changeState(name: "test2") + + XCTAssertFalse(entity.has(MockComponent.self)) + } + + func testEnterSecondStateDoesNotRemoveOverlappingComponents() { + let state1 = EntityState() + let component1 = MockComponent() + state1.add(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) + 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.add(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) + fsm.addState(name: "test2", state: state2) + fsm.changeState(name: "test2") + + XCTAssertTrue(entity.get(component: MockComponent.self) === component3) + } + + 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 = "" + } + } } From 81e18ba4439b1ddf020df6c1a8a66fa4dcd20740 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Tue, 29 Sep 2020 12:19:47 +0300 Subject: [PATCH 05/26] adds StateName generic to EntityStateMachine --- Sources/FirebladeECS/FSM.swift | 10 +++++----- Tests/FirebladeECSTests/FSMTests.swift | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 1412f71..2fe678f 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -166,8 +166,8 @@ public class StateComponentMapping { // MARK: - -public class EntityStateMachine { - private var states: [String: EntityState] +public class EntityStateMachine { + private var states: [StateName: EntityState] private var currentState: EntityState? @@ -178,18 +178,18 @@ public class EntityStateMachine { states = [:] } - @discardableResult public func addState(name: String, state: EntityState) -> Self { + @discardableResult public func addState(name: StateName, state: EntityState) -> Self { states[name] = state return self } - public func createState(name: String) -> EntityState { + public func createState(name: StateName) -> EntityState { let state = EntityState() states[name] = state return state } - public func changeState(name: String) { + public func changeState(name: StateName) { guard let newState = states[name] else { fatalError("Entity state '\(name)' doesn't exist") } diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 64fc208..70e1b25 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -223,9 +223,8 @@ class EntityStateTests: XCTestCase { } class EntityStateMachineTests: XCTestCase { - var nexus = Nexus() - var fsm = EntityStateMachine(entity: .init(nexus: .init(), id: .invalid)) + var fsm = EntityStateMachine(entity: .init(nexus: .init(), id: .invalid)) var entity = Entity(nexus: .init(), id: .init(rawValue: 1)) override func setUp() { From 720ba3f5650d2310a943acf9c0d46a1a9f3556b9 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Tue, 29 Sep 2020 16:37:44 +0300 Subject: [PATCH 06/26] update testEnterSecondStateDoesNotRemoveOverlappingComponents --- Tests/FirebladeECSTests/FSMTests.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 70e1b25..3cf2b54 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -274,6 +274,17 @@ class EntityStateMachineTests: XCTestCase { } 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.add(MockComponent.self).withInstance(component1) From 7f675db01d04e7ad02cff4eb23479492df50481b Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Tue, 29 Sep 2020 20:01:20 +0300 Subject: [PATCH 07/26] add documentation for component providers --- Sources/FirebladeECS/FSM.swift | 79 ++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 2fe678f..833afed 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -1,29 +1,57 @@ +/// Requires initializer with no arguments. +/// In case of component - makes sure it can be instantiated by component provider public protocol EmptyInitializable { init() } public typealias ComponentInitializable = Component & EmptyInitializable +/// 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 confirms 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: - -public struct ComponentInstanceProvider { +/// This component provider always returns the same instance of the component. The instance +/// is passed to the provider at initialisation. +public 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 } @@ -31,41 +59,60 @@ extension ComponentInstanceProvider: ComponentProvider { // MARK: - -public struct ComponentTypeProvider { +/// 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 { 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 - public init(type: T.Type) { + /// 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 class ComponentSingletonProvider { lazy private var instance: Component = { componentType.init() }() + private var componentType: ComponentInitializable.Type - public var identifier: AnyHashable { - ObjectIdentifier(instance) - } - + /// 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 } @@ -73,25 +120,39 @@ extension ComponentSingletonProvider: ComponentProvider { // MARK: - -public struct DynamicComponentProvider { +/// 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 { + /// Wrapper for closure to make it hashable public class Closure { let closure: () -> Component + + /// Initializer + /// - Parameter closure: Swift closure returning component of the appropriate type public init(closure: @escaping () -> Component) { self.closure = closure } } 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.closure() } From 121f8d55b0c9bcb9de881f2525bd36a0ee0b895c Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Wed, 30 Sep 2020 11:05:10 +0300 Subject: [PATCH 08/26] add documentation for StateComponentMapping --- Sources/FirebladeECS/FSM.swift | 66 +++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 833afed..4452682 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -137,7 +137,8 @@ public class DynamicComponentProvider { /// Initializer - /// - Parameter closure: Instance of Closure class. A wrapper around closure that will return the component instance when called. + /// - 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 } @@ -160,62 +161,109 @@ extension DynamicComponentProvider: ComponentProvider { // 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() { } + public init() {} - @discardableResult public func add(_ type: C.Type) -> StateComponentMapping { + /// Add a new ComponentMapping 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 { StateComponentMapping(creatingState: self, type: type) } - public func get(_ type: C.Type) -> ComponentProvider? { + /// 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? { providers[type.identifier] } - public func has(_ type: C.Type) -> Bool { + /// 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 { providers[type.identifier] != nil } } // 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 - public init(creatingState: EntityState, type: T.Type) { + /// 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) } + /// 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 } - @discardableResult public func withType(_ type: T.Type) -> 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 } - @discardableResult public func withSingleton(_ type: T.Type?) -> 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 method: The method to return the component instance + /// - Returns: This ComponentMapping, so more modifications can be applied @discardableResult public func withMethod(_ closure: DynamicComponentProvider.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 } - public func add(_ type: T.Type) -> StateComponentMapping { + /// Maps through to the add 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 + public func add(_ type: ComponentInitializable.Type) -> StateComponentMapping { creatingState.add(type) } From 386350ab1fff37f236efd69d1e17918f47ba8cb9 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Wed, 30 Sep 2020 12:09:08 +0300 Subject: [PATCH 09/26] add documentation for EntityStateMachine --- Sources/FirebladeECS/FSM.swift | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 4452682..bb4f2b8 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -190,8 +190,8 @@ public class EntityState { providers[type.identifier] != nil } } -// MARK: - +// MARK: - /// Used by the EntityState class to create the mappings of components to providers via a fluent interface. public class StateComponentMapping { @@ -275,29 +275,49 @@ public class StateComponentMapping { // 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 StateName: Generic hashable state name type public class EntityStateMachine { private var states: [StateName: 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: StateName, 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: StateName) -> 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: StateName) { guard let newState = states[name] else { fatalError("Entity state '\(name)' doesn't exist") @@ -306,7 +326,9 @@ public class EntityStateMachine { if newState === currentState { return } + var toAdd: [ComponentIdentifier: ComponentProvider] + if let currentState = currentState { toAdd = .init() for t in newState.providers { From 53d2c27082f280fd4d9816d9f0daa022cec9e296 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Wed, 30 Sep 2020 16:11:35 +0300 Subject: [PATCH 10/26] address linter warnings --- Sources/FirebladeECS/FSM.swift | 120 +++++++++++++------------ Tests/FirebladeECSTests/FSMTests.swift | 1 - 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index bb4f2b8..72c3c35 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -22,7 +22,7 @@ public protocol ComponentProvider { /// - Returns: struct/class instance that confirms 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 @@ -34,7 +34,7 @@ public protocol ComponentProvider { /// is passed to the provider at initialisation. public class ComponentInstanceProvider { private var instance: Component - + /// Initializer /// - Parameter instance: The instance to return whenever a component is requested. public init(instance: Component) { @@ -49,7 +49,7 @@ extension ComponentInstanceProvider: ComponentProvider { public var identifier: AnyHashable { ObjectIdentifier(instance) } - + /// Used to request a component from this provider /// - Returns: The instance public func getComponent() -> Component { @@ -63,12 +63,12 @@ extension ComponentInstanceProvider: ComponentProvider { /// is created when requested and is of the type passed in to the initializer. public 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) { @@ -90,12 +90,12 @@ 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 { - lazy private var instance: Component = { + 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) { @@ -110,7 +110,7 @@ extension ComponentSingletonProvider: ComponentProvider { public var identifier: AnyHashable { ObjectIdentifier(instance) } - + /// Used to request a component from this provider /// - Returns: The single instance public func getComponent() -> Component { @@ -125,17 +125,17 @@ extension ComponentSingletonProvider: ComponentProvider { public class DynamicComponentProvider { /// Wrapper for closure to make it hashable public class Closure { - let closure: () -> Component - + let provideComponent: () -> Component + /// Initializer - /// - Parameter closure: Swift closure returning component of the appropriate type - public init(closure: @escaping () -> Component) { - self.closure = closure + /// - Parameter provideComponent: Swift closure returning component of the appropriate type + public init(provideComponent: @escaping () -> Component) { + 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. @@ -151,11 +151,11 @@ extension DynamicComponentProvider: ComponentProvider { 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.closure() + closure.provideComponent() } } @@ -165,24 +165,25 @@ extension DynamicComponentProvider: ComponentProvider { /// 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 ComponentMapping 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 { + @discardableResult + public func add(_ 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? { 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 @@ -198,7 +199,7 @@ 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. @@ -210,26 +211,28 @@ public class StateComponentMapping { componentType = type provider = ComponentTypeProvider(type: type) } - + /// 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 { + @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 { + @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 @@ -237,28 +240,31 @@ public class StateComponentMapping { /// - 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 { + @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 method: The method to return the component instance /// - Returns: This ComponentMapping, so more modifications can be applied - @discardableResult public func withMethod(_ closure: DynamicComponentProvider.Closure) -> Self { + @discardableResult + public func withMethod(_ closure: DynamicComponentProvider.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 { + @discardableResult + public func withProvider(_ provider: ComponentProvider) -> Self { setProvider(provider) return self } - + /// Maps through to the add 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 @@ -266,7 +272,7 @@ public class StateComponentMapping { public func add(_ type: ComponentInitializable.Type) -> StateComponentMapping { creatingState.add(type) } - + private func setProvider(_ provider: ComponentProvider) { self.provider = provider creatingState.providers[componentType.identifier] = provider @@ -282,28 +288,29 @@ public class StateComponentMapping { /// - Parameter StateName: Generic hashable state name type public class EntityStateMachine { private var states: [StateName: 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: StateName, state: EntityState) -> Self { + @discardableResult + public func addState(name: StateName, 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 @@ -313,8 +320,7 @@ public class EntityStateMachine { 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. @@ -322,33 +328,33 @@ public class EntityStateMachine { guard let newState = states[name] else { fatalError("Entity state '\(name)' doesn't exist") } - + if newState === currentState { return } - + var toAdd: [ComponentIdentifier: ComponentProvider] - + if let currentState = currentState { toAdd = .init() - for t in newState.providers { - toAdd[t.key] = t.value + for (identifier, provider) in newState.providers { + toAdd[identifier] = provider } - - for t in currentState.providers { - if let other = toAdd[t.key], let current = currentState.providers[t.key], - current.identifier == other.identifier { - toAdd[t.key] = nil + + 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(t.key) + entity.remove(identifier) } } } else { toAdd = newState.providers } - - for t in toAdd { - guard let component = toAdd[t.key]?.getComponent() else { + + for (identifier, _) in toAdd { + guard let component = toAdd[identifier]?.getComponent() else { continue } entity.assign(component) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 3cf2b54..bb03c45 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -72,7 +72,6 @@ class ComponentTypeProviderTests: XCTestCase { self.value = false } } - } class ComponentSingletonProviderTests: XCTestCase { From efa06d5a27a96194011ae759742d8d3d13d03071 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Wed, 30 Sep 2020 16:59:34 +0300 Subject: [PATCH 11/26] update EntityStateTests --- Tests/FirebladeECSTests/FSMTests.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index bb03c45..5809032 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -169,6 +169,7 @@ class EntityStateTests: XCTestCase { func testAddWithNoQualifierCreatesTypeProvider() { state.add(MockComponent.self) let provider = state.providers[MockComponent.identifier] + XCTAssertNotNil(provider) XCTAssertTrue(provider is ComponentTypeProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent?) } @@ -176,6 +177,7 @@ class EntityStateTests: XCTestCase { func testAddWithTypeQualifierCreatesTypeProvider() { state.add(MockComponent.self).withType(MockComponent2.self) let provider = state.providers[MockComponent.identifier] + XCTAssertNotNil(provider) XCTAssertTrue(provider is ComponentTypeProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent2?) } @@ -202,6 +204,7 @@ class EntityStateTests: XCTestCase { state.add(MockComponent.self).withMethod(dynamickProvider) let provider = state.providers[MockComponent.identifier] + XCTAssertNotNil(provider) XCTAssertTrue(provider is DynamicComponentProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent) } From 8e7cc4528262b4716e2fe27c165c4f619c9f3c58 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Wed, 30 Sep 2020 17:04:58 +0300 Subject: [PATCH 12/26] add linter autocorrection for tests; auto update XCTestManifests to take fsm tests into account --- Tests/FirebladeECSTests/FSMTests.swift | 112 +++++++++--------- Tests/FirebladeECSTests/XCTestManifests.swift | 78 ++++++++++++ 2 files changed, 134 insertions(+), 56 deletions(-) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 5809032..e831525 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -8,23 +8,23 @@ class ComponentInstanceProviderTests: XCTestCase { 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 } @@ -37,29 +37,29 @@ class ComponentTypeProviderTests: XCTestCase { 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, EmptyInitializable { var value: String - + required init() { self.value = "" } @@ -67,7 +67,7 @@ class ComponentTypeProviderTests: XCTestCase { class MockComponent2: Component, EmptyInitializable { var value: Bool - + required init() { self.value = false } @@ -86,32 +86,32 @@ class ComponentSingletonProviderTests: XCTestCase { 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, EmptyInitializable { var value: Int - + required init() { self.value = 0 } } - + class MockComponent2: Component, EmptyInitializable { var value: String - + required init() { self.value = "" } @@ -126,7 +126,7 @@ class DynamicComponentProviderTests: XCTestCase { let component = provider.getComponent() as? MockComponent XCTAssertTrue(component === instance) } - + func testProvidersWithSameMethodHaveSameIdentifier() { let instance = MockComponent(value: 0) let providerMethod = DynamicComponentProvider.Closure { instance } @@ -134,7 +134,7 @@ class DynamicComponentProviderTests: XCTestCase { let provider2 = DynamicComponentProvider(closure: providerMethod) XCTAssertEqual(provider1.identifier, provider2.identifier) } - + func testProvidersWithDifferentMethodsHaveDifferentIdentifier() { let instance = MockComponent(value: 0) let providerMethod1 = DynamicComponentProvider.Closure { instance } @@ -143,10 +143,10 @@ class DynamicComponentProviderTests: XCTestCase { let provider2 = DynamicComponentProvider(closure: providerMethod2) XCTAssertNotEqual(provider1.identifier, provider2.identifier) } - + class MockComponent: Component { let value: Int - + init(value: Int) { self.value = value } @@ -157,15 +157,15 @@ class DynamicComponentProviderTests: XCTestCase { class EntityStateTests: XCTestCase { private var state = EntityState() - + override func setUp() { state = EntityState() } - + override func tearDown() { state = EntityState() } - + func testAddWithNoQualifierCreatesTypeProvider() { state.add(MockComponent.self) let provider = state.providers[MockComponent.identifier] @@ -173,7 +173,7 @@ class EntityStateTests: XCTestCase { XCTAssertTrue(provider is ComponentTypeProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent?) } - + func testAddWithTypeQualifierCreatesTypeProvider() { state.add(MockComponent.self).withType(MockComponent2.self) let provider = state.providers[MockComponent.identifier] @@ -181,7 +181,7 @@ class EntityStateTests: XCTestCase { XCTAssertTrue(provider is ComponentTypeProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent2?) } - + func testAddWithInstanceQualifierCreatesInstanceProvider() { let component = MockComponent() state.add(MockComponent.self).withInstance(component) @@ -189,38 +189,38 @@ class EntityStateTests: XCTestCase { XCTAssertTrue(provider is ComponentInstanceProvider?) XCTAssertTrue(provider?.getComponent() === component) } - + func testAddWithSingletonQualifierCreatesSingletonProvider() { state.add(MockComponent.self).withSingleton(MockComponent.self) let provider = state.providers[MockComponent.identifier] XCTAssertTrue(provider is ComponentSingletonProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent?) } - + func testAddWithMethodQualifierCreatesDynamicProvider() { let dynamickProvider = DynamicComponentProvider.Closure { MockComponent() } - + state.add(MockComponent.self).withMethod(dynamickProvider) let provider = state.providers[MockComponent.identifier] XCTAssertNotNil(provider) XCTAssertTrue(provider is DynamicComponentProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent) } - + class MockComponent: ComponentInitializable { let value: Int - + init(value: Int) { self.value = value } - + required init() { self.value = 0 } } - + class MockComponent2: MockComponent {} } @@ -228,13 +228,13 @@ class EntityStateMachineTests: XCTestCase { var nexus = Nexus() var fsm = EntityStateMachine(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() @@ -243,46 +243,46 @@ class EntityStateMachineTests: XCTestCase { fsm.changeState(name: "test") XCTAssertTrue(entity.get(component: MockComponent.self) === component) } - + func testEnterSecondStateAddsSecondStatesComponents() { let state1 = EntityState() let component1 = MockComponent() state1.add(MockComponent.self).withInstance(component1) fsm.addState(name: "test1", state: state1) - + let state2 = EntityState() let component2 = MockComponent2() state2.add(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.add(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) 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.") + XCTAssertFalse(event is ComponentRemoved, "Component was removed when it shouldn't have been.") } - + func nexusNonFatalError(_ message: String) {} } let delgate = EventDelegate() @@ -292,24 +292,24 @@ class EntityStateMachineTests: XCTestCase { state1.add(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) 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.add(MockComponent.self).withInstance(component1) fsm.addState(name: "test1", state: state1) fsm.changeState(name: "test1") - + let state2 = EntityState() let component3 = MockComponent() let component2 = MockComponent2() @@ -317,29 +317,29 @@ class EntityStateMachineTests: XCTestCase { state2.add(MockComponent2.self).withInstance(component2) fsm.addState(name: "test2", state: state2) fsm.changeState(name: "test2") - + XCTAssertTrue(entity.get(component: MockComponent.self) === component3) } - + 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 = "" } diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index f29280b..10bd937 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -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,32 @@ extension EntityIdGenTests { ] } +extension EntityStateMachineTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__EntityStateMachineTests = [ + ("testEnterSecondStateAddsSecondStatesComponents", testEnterSecondStateAddsSecondStatesComponents), + ("testEnterSecondStateDoesNotRemoveOverlappingComponents", testEnterSecondStateDoesNotRemoveOverlappingComponents), + ("testEnterSecondStateRemovesDifferentComponentsOfSameType", testEnterSecondStateRemovesDifferentComponentsOfSameType), + ("testEnterSecondStateRemovesFirstStatesComponents", testEnterSecondStateRemovesFirstStatesComponents), + ("testEnterStateAddsStatesComponents", testEnterStateAddsStatesComponents) + ] +} + +extension EntityStateTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__EntityStateTests = [ + ("testAddWithInstanceQualifierCreatesInstanceProvider", testAddWithInstanceQualifierCreatesInstanceProvider), + ("testAddWithMethodQualifierCreatesDynamicProvider", testAddWithMethodQualifierCreatesDynamicProvider), + ("testAddWithNoQualifierCreatesTypeProvider", testAddWithNoQualifierCreatesTypeProvider), + ("testAddWithSingletonQualifierCreatesSingletonProvider", testAddWithSingletonQualifierCreatesSingletonProvider), + ("testAddWithTypeQualifierCreatesTypeProvider", testAddWithTypeQualifierCreatesTypeProvider) + ] +} + extension EntityTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -303,9 +375,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), From c6a2bb774027019a33ae97cb8ea0f46e76aaf861 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Wed, 30 Sep 2020 17:37:53 +0300 Subject: [PATCH 13/26] adds missing registration of provider in StateComponentMapping init --- Sources/FirebladeECS/FSM.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 72c3c35..d99ac8d 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -210,6 +210,7 @@ public class StateComponentMapping { self.creatingState = creatingState componentType = type provider = ComponentTypeProvider(type: type) + creatingState.providers[componentType.identifier] = provider } /// Creates a mapping for the component type to a specific component instance. A From 65fedb737ecd878f2de9f8983ec5cd67ad0f728b Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 1 Oct 2020 10:26:33 +0300 Subject: [PATCH 14/26] add tests for EntityState.get --- Sources/FirebladeECS/FSM.swift | 2 +- Tests/FirebladeECSTests/FSMTests.swift | 44 +++++++++++++++++++ Tests/FirebladeECSTests/XCTestManifests.swift | 8 +++- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index d99ac8d..9b91234 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -123,7 +123,7 @@ 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 { - /// Wrapper for closure to make it hashable + /// Wrapper for closure to make it hashable via ObjectIdentifier public class Closure { let provideComponent: () -> Component diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index e831525..66fc41e 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -209,6 +209,50 @@ class EntityStateTests: XCTestCase { XCTAssertTrue(provider?.getComponent() is MockComponent) } + func testGetReturnsTypeProviderByDefault() { + state.add(MockComponent.self) + let provider = state.get(MockComponent.self) + XCTAssertNotNil(provider) + XCTAssertTrue(provider is ComponentTypeProvider?) + } + + func testGetReturnsInstanceProvider() { + let component = MockComponent() + state.add(MockComponent.self).withInstance(component) + let provider = state.get(MockComponent.self) + XCTAssertNotNil(provider) + XCTAssertTrue(provider is ComponentInstanceProvider?) + } + + func testGetReturnsSingletonProvider() { + state.add(MockComponent.self).withSingleton(MockComponent.self) + let provider = state.get(MockComponent.self) + XCTAssertNotNil(provider) + XCTAssertTrue(provider is ComponentSingletonProvider?) + } + + func testGetReturnsDynamicProvider() { + state.add(MockComponent.self).withMethod(.init { MockComponent() }) + let provider = state.get(MockComponent.self) + XCTAssertNotNil(provider) + XCTAssertTrue(provider is DynamicComponentProvider?) + } + + func testGetReturnsTypeProvider() { + state.add(MockComponent.self).withType(MockComponent.self) + let provider = state.get(MockComponent.self) + XCTAssertNotNil(provider) + XCTAssertTrue(provider is ComponentTypeProvider?) + } + + func testGetReturnsPassedProvider() { + let singletonProvider = ComponentSingletonProvider(type: MockComponent.self) + state.add(MockComponent.self).withProvider(singletonProvider) + let provider = state.get(MockComponent.self) as? ComponentSingletonProvider + XCTAssertNotNil(provider) + XCTAssertTrue(provider === singletonProvider) + } + class MockComponent: ComponentInitializable { let value: Int diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 10bd937..6588b7c 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -113,7 +113,13 @@ extension EntityStateTests { ("testAddWithMethodQualifierCreatesDynamicProvider", testAddWithMethodQualifierCreatesDynamicProvider), ("testAddWithNoQualifierCreatesTypeProvider", testAddWithNoQualifierCreatesTypeProvider), ("testAddWithSingletonQualifierCreatesSingletonProvider", testAddWithSingletonQualifierCreatesSingletonProvider), - ("testAddWithTypeQualifierCreatesTypeProvider", testAddWithTypeQualifierCreatesTypeProvider) + ("testAddWithTypeQualifierCreatesTypeProvider", testAddWithTypeQualifierCreatesTypeProvider), + ("testGetReturnsDynamicProvider", testGetReturnsDynamicProvider), + ("testGetReturnsInstanceProvider", testGetReturnsInstanceProvider), + ("testGetReturnsPassedProvider", testGetReturnsPassedProvider), + ("testGetReturnsSingletonProvider", testGetReturnsSingletonProvider), + ("testGetReturnsTypeProvider", testGetReturnsTypeProvider), + ("testGetReturnsTypeProviderByDefault", testGetReturnsTypeProviderByDefault) ] } From c222234adcce1453272765bcf8b2c085dfd74d93 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 1 Oct 2020 12:47:21 +0300 Subject: [PATCH 15/26] replaces fatalError with assertionFailure in 'changeState' --- Sources/FirebladeECS/FSM.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 9b91234..cd7309e 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -270,6 +270,7 @@ public class StateComponentMapping { /// 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) } @@ -327,7 +328,8 @@ public class EntityStateMachine { /// - Parameter name: The name of the state to change to. public func changeState(name: StateName) { guard let newState = states[name] else { - fatalError("Entity state '\(name)' doesn't exist") + assertionFailure("Entity state '\(name)' doesn't exist") + return } if newState === currentState { From 290554141375d551ab548bee961bd9efc7cfc304 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 1 Oct 2020 12:47:59 +0300 Subject: [PATCH 16/26] add StateComponentMappingTests --- Tests/FirebladeECSTests/FSMTests.swift | 66 ++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 66fc41e..e3dc5a3 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -31,6 +31,8 @@ class ComponentInstanceProviderTests: XCTestCase { } } +// MARK: - + class ComponentTypeProviderTests: XCTestCase { func testProviderReturnsAnInstanceOfType() { let provider = ComponentTypeProvider(type: MockComponent.self) @@ -74,6 +76,8 @@ class ComponentTypeProviderTests: XCTestCase { } } +// MARK: - + class ComponentSingletonProviderTests: XCTestCase { func testProviderReturnsAnInstanceOfType() { let provider = ComponentSingletonProvider(type: MockComponent.self) @@ -118,6 +122,8 @@ class ComponentSingletonProviderTests: XCTestCase { } } +// MARK: - + class DynamicComponentProviderTests: XCTestCase { func testProviderReturnsTheInstance() { let instance = MockComponent(value: 0) @@ -153,6 +159,8 @@ class DynamicComponentProviderTests: XCTestCase { } } +// MARK: - + @testable import class FirebladeECS.EntityState class EntityStateTests: XCTestCase { @@ -252,6 +260,17 @@ class EntityStateTests: XCTestCase { XCTAssertNotNil(provider) XCTAssertTrue(provider === singletonProvider) } + + func testHasReturnsFalseForNotCreatedProvider() { + XCTAssertFalse(state.has(MockComponent.self)) + } + + func testHasReturnsTrueForCreatedProvider() { + state.add(MockComponent.self) + XCTAssertTrue(state.has(MockComponent.self)) + } + + // TODO: continue here class MockComponent: ComponentInitializable { let value: Int @@ -268,6 +287,8 @@ class EntityStateTests: XCTestCase { class MockComponent2: MockComponent {} } +// MARK: - + class EntityStateMachineTests: XCTestCase { var nexus = Nexus() var fsm = EntityStateMachine(entity: .init(nexus: .init(), id: .invalid)) @@ -389,3 +410,48 @@ class EntityStateMachineTests: XCTestCase { } } } + +class StateComponentMappingTests: XCTestCase { + func testAddReturnsSameMappingForSameComponentType() { + let state = EntityState() + let mapping = state.add(MockComponent.self) + XCTAssertFalse(mapping === mapping.add(MockComponent.self)) + } + + func testAddReturnsSameMappingForDifferentComponentTypes() { + let state = EntityState() + let mapping = state.add(MockComponent.self) + XCTAssertFalse(mapping === mapping.add(MockComponent2.self)) + } + + func testAddAddsProviderToState() { + let state = EntityState() + let mapping = state.add(MockComponent.self) + mapping.add(MockComponent2.self) + XCTAssertTrue(state.has(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 = "" + } + } +} From 3e30812c99be06e809d77629b6b7b078a1782816 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 1 Oct 2020 12:53:42 +0300 Subject: [PATCH 17/26] auto update test manifest --- Tests/FirebladeECSTests/FSMTests.swift | 14 +++++++------- Tests/FirebladeECSTests/XCTestManifests.swift | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index e3dc5a3..52194d6 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -260,16 +260,16 @@ class EntityStateTests: XCTestCase { XCTAssertNotNil(provider) XCTAssertTrue(provider === singletonProvider) } - + func testHasReturnsFalseForNotCreatedProvider() { XCTAssertFalse(state.has(MockComponent.self)) } - + func testHasReturnsTrueForCreatedProvider() { state.add(MockComponent.self) XCTAssertTrue(state.has(MockComponent.self)) } - + // TODO: continue here class MockComponent: ComponentInitializable { @@ -417,20 +417,20 @@ class StateComponentMappingTests: XCTestCase { let mapping = state.add(MockComponent.self) XCTAssertFalse(mapping === mapping.add(MockComponent.self)) } - + func testAddReturnsSameMappingForDifferentComponentTypes() { let state = EntityState() let mapping = state.add(MockComponent.self) XCTAssertFalse(mapping === mapping.add(MockComponent2.self)) } - + func testAddAddsProviderToState() { let state = EntityState() let mapping = state.add(MockComponent.self) mapping.add(MockComponent2.self) XCTAssertTrue(state.has(MockComponent.self)) } - + class MockComponent: ComponentInitializable { let value: Int @@ -442,7 +442,7 @@ class StateComponentMappingTests: XCTestCase { self.value = 0 } } - + class MockComponent2: ComponentInitializable { let value: String diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 6588b7c..fc0efde 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -119,7 +119,9 @@ extension EntityStateTests { ("testGetReturnsPassedProvider", testGetReturnsPassedProvider), ("testGetReturnsSingletonProvider", testGetReturnsSingletonProvider), ("testGetReturnsTypeProvider", testGetReturnsTypeProvider), - ("testGetReturnsTypeProviderByDefault", testGetReturnsTypeProviderByDefault) + ("testGetReturnsTypeProviderByDefault", testGetReturnsTypeProviderByDefault), + ("testHasReturnsFalseForNotCreatedProvider", testHasReturnsFalseForNotCreatedProvider), + ("testHasReturnsTrueForCreatedProvider", testHasReturnsTrueForCreatedProvider) ] } @@ -369,6 +371,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` @@ -406,6 +419,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) ] } From 7bd1b3cc821e1f56c347fa86099a84ce098ff011 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 1 Oct 2020 18:16:15 +0300 Subject: [PATCH 18/26] add tests for EntityStateMachine.createState --- Tests/FirebladeECSTests/FSMTests.swift | 17 +++++++++++++++++ Tests/FirebladeECSTests/XCTestManifests.swift | 2 ++ 2 files changed, 19 insertions(+) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 52194d6..5d6fa3a 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -386,6 +386,21 @@ class EntityStateMachineTests: XCTestCase { XCTAssertTrue(entity.get(component: MockComponent.self) === component3) } + func testCreateStateAddsState() { + let state = fsm.createState(name: "test") + let component = MockComponent() + state.add(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.add(MockComponent.self).withInstance(component) + XCTAssertNil(entity.get(component: MockComponent.self)) + } + class MockComponent: ComponentInitializable { let value: Int @@ -411,6 +426,8 @@ class EntityStateMachineTests: XCTestCase { } } +// MARK: - + class StateComponentMappingTests: XCTestCase { func testAddReturnsSameMappingForSameComponentType() { let state = EntityState() diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index fc0efde..2e7526c 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -96,6 +96,8 @@ extension EntityStateMachineTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__EntityStateMachineTests = [ + ("testCreateStateAddsState", testCreateStateAddsState), + ("testCreateStateDoesNotChangeState", testCreateStateDoesNotChangeState), ("testEnterSecondStateAddsSecondStatesComponents", testEnterSecondStateAddsSecondStatesComponents), ("testEnterSecondStateDoesNotRemoveOverlappingComponents", testEnterSecondStateDoesNotRemoveOverlappingComponents), ("testEnterSecondStateRemovesDifferentComponentsOfSameType", testEnterSecondStateRemovesDifferentComponentsOfSameType), From 4995b16c43f4a6f364a1ad693ef7dc2e842fb28c Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 1 Oct 2020 19:52:08 +0300 Subject: [PATCH 19/26] add test for calling changeState with same name twice --- Tests/FirebladeECSTests/FSMTests.swift | 17 +++++++++++++++-- Tests/FirebladeECSTests/XCTestManifests.swift | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 5d6fa3a..1d3cc86 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -270,8 +270,6 @@ class EntityStateTests: XCTestCase { XCTAssertTrue(state.has(MockComponent.self)) } - // TODO: continue here - class MockComponent: ComponentInitializable { let value: Int @@ -401,6 +399,21 @@ class EntityStateMachineTests: XCTestCase { XCTAssertNil(entity.get(component: MockComponent.self)) } + func testCallChangeStateWithSameNameLeavesEntityComponentsIntact() { + let state = fsm.createState(name: "test") + let component1 = MockComponent() + let component2 = MockComponent2() + state.add(MockComponent.self).withInstance(component1) + state.add(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) + } + class MockComponent: ComponentInitializable { let value: Int diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 2e7526c..1613cc8 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -96,6 +96,7 @@ extension EntityStateMachineTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__EntityStateMachineTests = [ + ("testCallChangeStateWithSameNameLeavesEntityComponentsIntact", testCallChangeStateWithSameNameLeavesEntityComponentsIntact), ("testCreateStateAddsState", testCreateStateAddsState), ("testCreateStateDoesNotChangeState", testCreateStateDoesNotChangeState), ("testEnterSecondStateAddsSecondStatesComponents", testEnterSecondStateAddsSecondStatesComponents), From 1aae190fc1395317c0b3624240fd5dda5e717da3 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 1 Oct 2020 20:25:49 +0300 Subject: [PATCH 20/26] update changeState --- Sources/FirebladeECS/FSM.swift | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index cd7309e..d37fa41 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -203,14 +203,13 @@ public class StateComponentMapping { /// 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) - creatingState.providers[componentType.identifier] = provider + setProvider(provider) } /// Creates a mapping for the component type to a specific component instance. A @@ -286,7 +285,6 @@ 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] @@ -356,11 +354,8 @@ public class EntityStateMachine { toAdd = newState.providers } - for (identifier, _) in toAdd { - guard let component = toAdd[identifier]?.getComponent() else { - continue - } - entity.assign(component) + for (_, provider) in toAdd { + entity.assign(provider.getComponent()) } currentState = newState } From 1c8c063e014afbfb6ab55c94bdc8bf445cca0d35 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Fri, 2 Oct 2020 13:17:31 +0300 Subject: [PATCH 21/26] add test for EntityStateMachine could be deallocated --- Sources/FirebladeECS/FSM.swift | 2 +- Tests/FirebladeECSTests/FSMTests.swift | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index d37fa41..6167a84 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -354,7 +354,7 @@ public class EntityStateMachine { toAdd = newState.providers } - for (_, provider) in toAdd { + for (_, provider) in toAdd { entity.assign(provider.getComponent()) } currentState = newState diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 1d3cc86..f7dc428 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -413,6 +413,27 @@ class EntityStateMachineTests: XCTestCase { XCTAssertTrue(entity.get(component: MockComponent.self) === component1) XCTAssertTrue(entity.get(component: MockComponent2.self) === component2) } + + func testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity() { + class Marker: Component { + let fsm: EntityStateMachine + init(fsm: EntityStateMachine) { + self.fsm = fsm + } + } + + let nexus = Nexus() + var entity = nexus.createEntity() + var markerComponent = Marker(fsm: EntityStateMachine(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 From 39692bbfc2a3775d570503e7cb1f9be3f9a1e59d Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Sat, 3 Oct 2020 01:12:18 +0300 Subject: [PATCH 22/26] autoupdate manifest --- Sources/FirebladeECS/FSM.swift | 2 +- Tests/FirebladeECSTests/FSMTests.swift | 4 ++-- Tests/FirebladeECSTests/XCTestManifests.swift | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 6167a84..d9ab4d5 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -344,7 +344,7 @@ public class EntityStateMachine { for (identifier, _) in currentState.providers { if let other = toAdd[identifier], let current = currentState.providers[identifier], - current.identifier == other.identifier { + current.identifier == other.identifier { toAdd[identifier] = nil } else { entity.remove(identifier) diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index f7dc428..8c466e0 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -413,7 +413,7 @@ class EntityStateMachineTests: XCTestCase { XCTAssertTrue(entity.get(component: MockComponent.self) === component1) XCTAssertTrue(entity.get(component: MockComponent2.self) === component2) } - + func testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity() { class Marker: Component { let fsm: EntityStateMachine @@ -421,7 +421,7 @@ class EntityStateMachineTests: XCTestCase { self.fsm = fsm } } - + let nexus = Nexus() var entity = nexus.createEntity() var markerComponent = Marker(fsm: EntityStateMachine(entity: entity)) diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 1613cc8..8079fe2 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -103,7 +103,8 @@ extension EntityStateMachineTests { ("testEnterSecondStateDoesNotRemoveOverlappingComponents", testEnterSecondStateDoesNotRemoveOverlappingComponents), ("testEnterSecondStateRemovesDifferentComponentsOfSameType", testEnterSecondStateRemovesDifferentComponentsOfSameType), ("testEnterSecondStateRemovesFirstStatesComponents", testEnterSecondStateRemovesFirstStatesComponents), - ("testEnterStateAddsStatesComponents", testEnterStateAddsStatesComponents) + ("testEnterStateAddsStatesComponents", testEnterStateAddsStatesComponents), + ("testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity", testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity) ] } From 76c91d83f72243a6ba2bcf1dc34333402daf440b Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Sat, 3 Oct 2020 23:29:27 +0300 Subject: [PATCH 23/26] add methods to EntityState to improve ergonomics --- Sources/FirebladeECS/FSM.swift | 60 +++++++++++++++++++ Tests/FirebladeECSTests/FSMTests.swift | 35 +++++++++++ Tests/FirebladeECSTests/XCTestManifests.swift | 5 ++ 3 files changed, 100 insertions(+) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index d9ab4d5..01b22b4 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -192,6 +192,66 @@ public class EntityState { } } +/// 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(_ component: C) -> Self { + add(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 { + add(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 { + add(type).withSingleton(type) + return self + } + + /// Creates a mapping for the component type to a method call. + /// A DynamicComponentProvider is used for the mapping. + /// - Parameter method: The method to return the component instance + /// - Returns: This EntityState, so more modifications can be applied + @inline(__always) + @discardableResult + public func addMethod(type: C.Type, closure: DynamicComponentProvider.Closure) -> Self { + add(type).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(type: C.Type, provider: ComponentProvider) -> Self { + add(type).withProvider(provider) + return self + } +} + // MARK: - /// Used by the EntityState class to create the mappings of components to providers via a fluent interface. diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index 8c466e0..f31c372 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -270,6 +270,41 @@ class EntityStateTests: XCTestCase { XCTAssertTrue(state.has(MockComponent.self)) } + func testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType() { + let component = MockComponent() + state.addInstance(component) + XCTAssertTrue(state.get(MockComponent.self) is ComponentInstanceProvider?) + XCTAssert(state.get(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?) + } + + 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?) + } + + func testAddMethodCreatesMappingAndSetsDynamicProviderForType() { + let component = MockComponent() + state.addMethod(type: MockComponent.self, closure: .init { component }) + XCTAssertTrue(state.get(MockComponent.self) is DynamicComponentProvider?) + XCTAssertTrue(state.get(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)) + } + class MockComponent: ComponentInitializable { let value: Int diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 8079fe2..f950812 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -113,6 +113,11 @@ extension EntityStateTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__EntityStateTests = [ + ("testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType", testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType), + ("testAddMethodCreatesMappingAndSetsDynamicProviderForType", testAddMethodCreatesMappingAndSetsDynamicProviderForType), + ("testAddProviderCreatesMappingAndSetsProvider", testAddProviderCreatesMappingAndSetsProvider), + ("testAddSingletonCreatesMappingAndSetsSingletonProviderForType", testAddSingletonCreatesMappingAndSetsSingletonProviderForType), + ("testAddTypeCreatesMappingAndSetsTypeProviderForType", testAddTypeCreatesMappingAndSetsTypeProviderForType), ("testAddWithInstanceQualifierCreatesInstanceProvider", testAddWithInstanceQualifierCreatesInstanceProvider), ("testAddWithMethodQualifierCreatesDynamicProvider", testAddWithMethodQualifierCreatesDynamicProvider), ("testAddWithNoQualifierCreatesTypeProvider", testAddWithNoQualifierCreatesTypeProvider), From 2902b096a5cd7630a30c7c47af2fb46c5b115cc1 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Sat, 3 Oct 2020 23:48:26 +0300 Subject: [PATCH 24/26] add generic component type to DynamicComponentProvider --- Sources/FirebladeECS/FSM.swift | 16 ++++++++-------- Tests/FirebladeECSTests/FSMTests.swift | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/FirebladeECS/FSM.swift b/Sources/FirebladeECS/FSM.swift index 01b22b4..bc75c19 100644 --- a/Sources/FirebladeECS/FSM.swift +++ b/Sources/FirebladeECS/FSM.swift @@ -122,14 +122,14 @@ 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 class DynamicComponentProvider { /// Wrapper for closure to make it hashable via ObjectIdentifier public class Closure { - let provideComponent: () -> Component + let provideComponent: () -> C /// Initializer /// - Parameter provideComponent: Swift closure returning component of the appropriate type - public init(provideComponent: @escaping () -> Component) { + public init(provideComponent: @escaping () -> C) { self.provideComponent = provideComponent } } @@ -231,12 +231,12 @@ extension EntityState { /// Creates a mapping for the component type to a method call. /// A DynamicComponentProvider is used for the mapping. - /// - Parameter method: The method to return the component instance + /// - 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(type: C.Type, closure: DynamicComponentProvider.Closure) -> Self { - add(type).withMethod(closure) + public func addMethod(closure: DynamicComponentProvider.Closure) -> Self { + add(C.self).withMethod(closure) return self } @@ -308,10 +308,10 @@ public class StateComponentMapping { /// Creates a mapping for the component type to a method call. A /// DynamicComponentProvider is used for the mapping. - /// - Parameter method: The method to return the component instance + /// - Parameter closure: The Closure instance to return the component instance /// - Returns: This ComponentMapping, so more modifications can be applied @discardableResult - public func withMethod(_ closure: DynamicComponentProvider.Closure) -> Self { + public func withMethod(_ closure: DynamicComponentProvider.Closure) -> Self { setProvider(DynamicComponentProvider(closure: closure)) return self } diff --git a/Tests/FirebladeECSTests/FSMTests.swift b/Tests/FirebladeECSTests/FSMTests.swift index f31c372..28fa552 100644 --- a/Tests/FirebladeECSTests/FSMTests.swift +++ b/Tests/FirebladeECSTests/FSMTests.swift @@ -213,7 +213,7 @@ class EntityStateTests: XCTestCase { state.add(MockComponent.self).withMethod(dynamickProvider) let provider = state.providers[MockComponent.identifier] XCTAssertNotNil(provider) - XCTAssertTrue(provider is DynamicComponentProvider?) + XCTAssertTrue(provider is DynamicComponentProvider?) XCTAssertTrue(provider?.getComponent() is MockComponent) } @@ -243,7 +243,7 @@ class EntityStateTests: XCTestCase { state.add(MockComponent.self).withMethod(.init { MockComponent() }) let provider = state.get(MockComponent.self) XCTAssertNotNil(provider) - XCTAssertTrue(provider is DynamicComponentProvider?) + XCTAssertTrue(provider is DynamicComponentProvider?) } func testGetReturnsTypeProvider() { @@ -293,8 +293,8 @@ class EntityStateTests: XCTestCase { func testAddMethodCreatesMappingAndSetsDynamicProviderForType() { let component = MockComponent() - state.addMethod(type: MockComponent.self, closure: .init { component }) - XCTAssertTrue(state.get(MockComponent.self) is DynamicComponentProvider?) + state.addMethod(closure: .init { component }) + XCTAssertTrue(state.get(MockComponent.self) is DynamicComponentProvider?) XCTAssertTrue(state.get(MockComponent.self)?.getComponent() === component) } From 1c2bb78dc4238b09a854183fe4c93f3b95cee44f Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 8 Oct 2020 01:45:10 +0300 Subject: [PATCH 25/26] add changes proposed during code review --- Sources/FirebladeECS/FSM.swift | 61 +++++----- Tests/FirebladeECSTests/FSMTests.swift | 155 +++++++++++++------------ 2 files changed, 115 insertions(+), 101 deletions(-) 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 { From abb7990d6a418825a862cc2a7e0f6c87302658c5 Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Thu, 8 Oct 2020 01:48:40 +0300 Subject: [PATCH 26/26] add updated XCTestManifests --- Tests/FirebladeECSTests/XCTestManifests.swift | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index f950812..c9b2465 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -114,23 +114,23 @@ extension EntityStateTests { // 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), - ("testAddWithInstanceQualifierCreatesInstanceProvider", testAddWithInstanceQualifierCreatesInstanceProvider), - ("testAddWithMethodQualifierCreatesDynamicProvider", testAddWithMethodQualifierCreatesDynamicProvider), - ("testAddWithNoQualifierCreatesTypeProvider", testAddWithNoQualifierCreatesTypeProvider), - ("testAddWithSingletonQualifierCreatesSingletonProvider", testAddWithSingletonQualifierCreatesSingletonProvider), - ("testAddWithTypeQualifierCreatesTypeProvider", testAddWithTypeQualifierCreatesTypeProvider), - ("testGetReturnsDynamicProvider", testGetReturnsDynamicProvider), - ("testGetReturnsInstanceProvider", testGetReturnsInstanceProvider), - ("testGetReturnsPassedProvider", testGetReturnsPassedProvider), - ("testGetReturnsSingletonProvider", testGetReturnsSingletonProvider), - ("testGetReturnsTypeProvider", testGetReturnsTypeProvider), - ("testGetReturnsTypeProviderByDefault", testGetReturnsTypeProviderByDefault), - ("testHasReturnsFalseForNotCreatedProvider", testHasReturnsFalseForNotCreatedProvider), - ("testHasReturnsTrueForCreatedProvider", testHasReturnsTrueForCreatedProvider) + ("testHasProviderReturnsFalseForNotCreatedProvider", testHasProviderReturnsFalseForNotCreatedProvider), + ("testHasProviderReturnsTrueForCreatedProvider", testHasProviderReturnsTrueForCreatedProvider), + ("testProviderForTypeReturnsDynamicProvider", testProviderForTypeReturnsDynamicProvider), + ("testProviderForTypeReturnsInstanceProvider", testProviderForTypeReturnsInstanceProvider), + ("testProviderForTypeReturnsPassedProvider", testProviderForTypeReturnsPassedProvider), + ("testProviderForTypeReturnsSingletonProvider", testProviderForTypeReturnsSingletonProvider), + ("testProviderForTypeReturnsTypeProvider", testProviderForTypeReturnsTypeProvider), + ("testProviderForTypeReturnsTypeProviderByDefault", testProviderForTypeReturnsTypeProviderByDefault) ] }