Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #10

Merged
merged 15 commits into from
Aug 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions Sources/Tibei/client/ClientMessenger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,36 @@

import UIKit

/**
Represents a messenger that sends and receives messages from the client-side.
*/
public class ClientMessenger: Messenger {
var services: [String:NetService] = [:]
var isReady: Bool = false

/// The chain of registered message responders
public var responders: ResponderChain = ResponderChain()
var connection: Connection?
var serviceBrowser: GameControllerServiceBrowser
var serviceBrowser: TibeiServiceBrowser

/**
Default initializer.

Note that after this call, the `ClientMessenger` instance won't be browsing for services yet.

- SeeAlso: ```browseForServices(withIdentifier:)```
*/
public init() {
self.serviceBrowser = GameControllerServiceBrowser()
self.serviceBrowser = TibeiServiceBrowser()

self.serviceBrowser.delegate = self
}

/**
Browses for services that are currently being published via Bonjour with a certain service identifier.

- Parameter serviceIdentifier: Service identifier being currently browsed for
*/
public func browseForServices(withIdentifier serviceIdentifier: String) {
if self.serviceBrowser.isBrowsing {
if !self.services.isEmpty {
Expand All @@ -34,12 +50,18 @@ public class ClientMessenger: Messenger {
self.serviceBrowser.startBrowsing(forServiceType: serviceIdentifier)
}

/// Stops browsing for services.
/// ConnectionResponders will stop receiving `availableServicesChanged` calls after this is called.
public func stopBrowsingForServices() {
self.serviceBrowser.stopBrowsing()
}

public func connect(serviceName: String) throws {
guard let service = self.services[serviceName] else {
/// Connects to a service based on its provider's identifier
///
/// - Parameter serviceName: The name of the service to connect to
/// - Throws: `ConnectionError.inexistentService` if the `serviceName` parameter is provided, and `ConnectionError.connectionFailure` if some error occurred while obtaining input stream from connection
public func connect(serviceIdentifier: String) throws {
guard let service = self.services[serviceIdentifier] else {
throw ConnectionError.inexistentService
}

Expand All @@ -57,13 +79,18 @@ public class ClientMessenger: Messenger {
newConnection.open()
}

/// Disconnects from the currently connected service. Does nothing if this messenger is not connected to any service.
public func disconnect() {
self.isReady = false

self.connection?.close()
self.connection = nil
}

/// Sends a message to the currently active connection.
///
/// - Parameter message: Message to send.
/// - Throws: `ConnectionError.notConnected` if there is no connection to send the message to.
public func sendMessage<Message: JSONConvertibleMessage>(_ message: Message) throws {
guard self.isReady else {
throw ConnectionError.notConnected
Expand All @@ -72,6 +99,9 @@ public class ClientMessenger: Messenger {
self.connection?.sendMessage(message)
}

/// Registers a new responder to this messenger's responder chain.
///
/// - Parameter responder: Responder to register in the chain.
public func registerResponder(_ responder: ConnectionResponder) {
if responder is ClientConnectionResponder {
self.responders.append(ClientResponderChainNode(responder: responder))
Expand All @@ -83,13 +113,13 @@ public class ClientMessenger: Messenger {

// MARK: - GameControllerServiceBrowserDelegate protocol

extension ClientMessenger: GameControllerServiceBrowserDelegate {
func gameControllerServiceBrowser(_ browser: GameControllerServiceBrowser, raisedErrors errorDict: [String : NSNumber]) {
extension ClientMessenger: TibeiServiceBrowserDelegate {
func gameControllerServiceBrowser(_ browser: TibeiServiceBrowser, raisedErrors errorDict: [String : NSNumber]) {
print("Service browser raised errors:")
print(errorDict)
}

func gameControllerServiceBrowser(_ browser: GameControllerServiceBrowser, foundService service: NetService, moreComing: Bool) {
func gameControllerServiceBrowser(_ browser: TibeiServiceBrowser, foundService service: NetService, moreComing: Bool) {
self.services[service.name] = service

if !moreComing {
Expand All @@ -99,7 +129,7 @@ extension ClientMessenger: GameControllerServiceBrowserDelegate {
}
}

func gameControllerServiceBrowser(_ browser: GameControllerServiceBrowser, removedService service: NetService, moreComing: Bool) {
func gameControllerServiceBrowser(_ browser: TibeiServiceBrowser, removedService service: NetService, moreComing: Bool) {
self.services.removeValue(forKey: service.name)

if !moreComing {
Expand Down
15 changes: 0 additions & 15 deletions Sources/Tibei/client/GameControllerServiceBrowserDelegate.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

import UIKit

class GameControllerServiceBrowser: NSObject {
class TibeiServiceBrowser: NSObject {
let serviceBrowser: NetServiceBrowser = NetServiceBrowser()

var inputStream: InputStream?
var outputStream: OutputStream?

var isBrowsing: Bool = false
var delegate: GameControllerServiceBrowserDelegate?
var delegate: TibeiServiceBrowserDelegate?

override init() {
super.init()
Expand All @@ -33,7 +33,7 @@ class GameControllerServiceBrowser: NSObject {
}
}

extension GameControllerServiceBrowser: NetServiceBrowserDelegate {
extension TibeiServiceBrowser: NetServiceBrowserDelegate {
func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) {
self.isBrowsing = true
}
Expand Down
15 changes: 15 additions & 0 deletions Sources/Tibei/client/TibeiServiceBrowserDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// MessageSender.swift
// connectivityTest
//
// Created by Daniel de Jesus Oliveira on 15/11/2016.
// Copyright © 2016 Daniel de Jesus Oliveira. All rights reserved.
//

import Foundation

protocol TibeiServiceBrowserDelegate {
func gameControllerServiceBrowser(_ browser: TibeiServiceBrowser, raisedErrors errorDict: [String:NSNumber])
func gameControllerServiceBrowser(_ browser: TibeiServiceBrowser, foundService service: NetService, moreComing: Bool)
func gameControllerServiceBrowser(_ browser: TibeiServiceBrowser, removedService service: NetService, moreComing: Bool)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@

import Foundation

/**
A specialization of `ConnectionResponder` with additional behavior for the client side.
*/
public protocol ClientConnectionResponder: ConnectionResponder {
/**
If the `ClientMessenger` instance is browsing for services, this method gets called whenever a new service becomes available (or if an available service goes offline), receiving the updated service list as a parameter.
*/
func availableServicesChanged(availableServiceIDs: [String])
}

public extension ClientConnectionResponder {
/// Does nothing
func availableServicesChanged(availableServiceIDs: [String]) {
}
}
14 changes: 14 additions & 0 deletions Sources/Tibei/common/Messenger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,29 @@

import Foundation

/**
Represents an entity that receives messages from a connection. This protocol is implemented by both `ClientMessenger` and `ServerMessenger`.
*/
public protocol Messenger {
/// The chain of registered message responders
var responders: ResponderChain { get }
}

extension Messenger {
/**
Registers a new responder to the responder chain.

- Parameter responder: the `ConnectionResponder` to be registered to the chain.
*/
public func registerResponder(_ responder: ConnectionResponder) {
self.responders.append(ResponderChainNode(responder: responder))
}

/**
Removes a responder from the responder chain.

- Parameter responder: the `ConnectionResponder` to be removed from the chain.
*/
public func unregisterResponder(_ responder: ConnectionResponder) {
self.responders.remove(ResponderChainNode(responder: responder))
}
Expand Down
37 changes: 37 additions & 0 deletions Sources/Tibei/common/responderChain/ConnectionResponder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,66 @@
//
//

/**
Represents an entity that responds to received messages. It can explicitly state which types of messages it expects, and will only be prompted to process a message if appropriated.
*/
public protocol ConnectionResponder: class {
/**
An array of types representing the messages it wants to receive.
*/
var allowedMessages: [JSONConvertibleMessage.Type] { get }

/**
Processes a message that was received through an active connection.

- Parameters:
- message: The message received through the connection
- connectionID: The identifier of the connection that received the message
*/
func processMessage(_ message: JSONConvertibleMessage, fromConnectionWithID connectionID: ConnectionID)
/**
Notifies the responder that a connection has been accepted

- Parameter connectionID: The identifier of the accepted connection
*/
func acceptedConnection(withID connectionID: ConnectionID)
/**
Notifies the responder that a connection has been lost

- Parameter connectionID: The identifier of the lost connection
*/
func lostConnection(withID connectionID: ConnectionID)
/**
Processes an error that occurred while handling an active connection

- Parameters:
- error: The error that occurred.
- connectionID: The identifier of the connection that raised the error.
*/
func processError(_ error: Error, fromConnectionWithID connectionID: ConnectionID?)
}

public extension ConnectionResponder {
/**
An empty array. Note that this will cause the responder not to receive any messages. You have to explicitly state the messages that your responder will handle.
*/
var allowedMessages: [JSONConvertibleMessage.Type] {
return []
}

/// :nodoc:
func processMessage(_ message: JSONConvertibleMessage, fromConnectionWithID connectionID: ConnectionID) {
}

/// :nodoc:
func acceptedConnection(withID connectionID: ConnectionID) {
}

/// :nodoc:
func lostConnection(withID connectionID: ConnectionID) {
}

/// :nodoc:
func processError(_ error: Error, fromConnectionWithID connectionID: ConnectionID?) {
}
}
8 changes: 8 additions & 0 deletions Sources/Tibei/common/responderChain/ResponderChain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ class ResponderChainNode {
}
}

/**
In Tibei, incoming messages are processed by a chain of objects that may be set as responders in a chain. Responders must conform to the `ConnectionResponder` or `ClientConnectionResponder` protocols.

Currently, you have to manage the order in which elements are added to the chain, so take care if this is any relevant.

- SeeAlso: `ConnectionResponder`
- SeeAlso: `ClientConnectionResponder`
*/
public class ResponderChain {
var head: ResponderChainNode?
var tail: ResponderChainNode?
Expand Down
6 changes: 6 additions & 0 deletions Sources/Tibei/connection/Connection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@

import UIKit

/**
Represents a single connection between server and client. The same class is used on either side.
*/
public class Connection: NSObject, StreamDelegate {
let outwardMessagesQueue: OperationQueue = OperationQueue()
/// An unique identifier for the connection
public let identifier: ConnectionID

var input: InputStream
Expand All @@ -21,6 +25,7 @@ public class Connection: NSObject, StreamDelegate {
}
var isReady: Bool = false
var pingTimer = Timer()
/// :nodoc:
override public var hashValue: Int {
return self.identifier.id.hashValue
}
Expand Down Expand Up @@ -132,6 +137,7 @@ public class Connection: NSObject, StreamDelegate {

// MARK: - StreamDelegate protocol

/// :nodoc:
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.errorOccurred:
Expand Down
27 changes: 27 additions & 0 deletions Sources/Tibei/connection/ConnectionError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,42 @@

import Foundation

/**
An error that may occur at any point of the connection's lifecycle.
*/
public enum ConnectionError: Error {
/**
Thrown if the `OutputStream` of a `Connection` can't take any more data.
*/
case inputUnavailable
/**
Thrown if a responder receives a message with a `_type` field that is not valid (i.e. is not a string).
*/
case invalidMessageType([String:Any])
/**
Thrown if an error occurs while reading from a `Connection`'s `InputStream`.
*/
case inputError

/**
Thrown if an error occurs while writing to a `Connection`'s `OutputStream`.
*/
case outputError
/**
Thrown if a `Connection`'s output stream has no space left.
*/
case outputStreamUnavailable
/**
Thrown if an attempt to send a message is made without an active `Connection`.
*/
case notConnected

/**
Thrown if an attempt to connect to a service that doesn't exists is made.
*/
case inexistentService
/**
Thrown if an error occurred while trying to obtain a `Connection`'s input and output streams.
*/
case connectionFailure
}
5 changes: 5 additions & 0 deletions Sources/Tibei/connection/ConnectionID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@

import Foundation

/**
A struct that's used to identify existing connections. An uuid is generated upon this struct's instantiation, and its hashValue is used for comparison.
*/
public struct ConnectionID: Hashable {
let id: UUID

/// :nodoc:
public var hashValue: Int {
return self.id.hashValue
}
Expand All @@ -19,6 +23,7 @@ public struct ConnectionID: Hashable {
self.id = UUID()
}

/// :nodoc:
public static func ==(lhs: ConnectionID, rhs: ConnectionID) -> Bool {
return lhs.hashValue == rhs.hashValue
}
Expand Down
Loading