Skip to content

Commit

Permalink
Merge pull request #10 from Valbrand/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Valbrand committed Aug 23, 2017
2 parents 80edba4 + e29e388 commit f14852d
Show file tree
Hide file tree
Showing 63 changed files with 7,424 additions and 50 deletions.
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

0 comments on commit f14852d

Please sign in to comment.