Add initial implementation of component providers for entity FSM

This commit is contained in:
Igor Kravchenko 2020-09-17 20:17:52 +03:00
parent 36e8c79e82
commit acc2a0feff
2 changed files with 322 additions and 0 deletions

View File

@ -0,0 +1,167 @@
public protocol EmptyInitializable {
init()
}
public typealias ComponentInitializable = Component & EmptyInitializable
public protocol ComponentProvider {
var identifier: AnyHashable { get }
func getComponent<C>() -> 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>() -> C? where C : Component {
instance as? C
}
}
// MARK: -
public struct ComponentTypeProvider {
private var componentType: ComponentInitializable.Type
public let identifier: AnyHashable
public init<T: ComponentInitializable>(type: T.Type) {
componentType = type
identifier = ObjectIdentifier(componentType.self)
}
}
extension ComponentTypeProvider: ComponentProvider {
public func getComponent<C>() -> 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<T: ComponentInitializable>(type: T.Type) {
componentType = type
}
internal init(type: ComponentInitializable.Type) {
componentType = type
}
}
extension ComponentSingletonProvider: ComponentProvider {
public func getComponent<C>() -> 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>() -> C? where C: Component {
closure.closure() as? C
}
}
// MARK: -
public class EntityState {
internal var providers = [ComponentIdentifier: ComponentProvider]()
public init() { }
public func add<C: ComponentInitializable>(_ 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<T: ComponentInitializable>(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<T: ComponentInitializable>(_ type: T.Type) -> Self {
setProvider(ComponentTypeProvider(type: type))
return self
}
public func withSingleton<T: ComponentInitializable>(_ 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<T: ComponentInitializable>(_ type: T.Type) -> StateComponentMapping {
creatingState.add(type)
}
private func setProvider(_ provider: ComponentProvider) {
self.provider = provider
creatingState.providers[componentType.identifier] = provider
}
}

View File

@ -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
}
}
}