Coder Social home page Coder Social logo

dannys42 / causality Goto Github PK

View Code? Open in Web Editor NEW
10.0 1.0 1.0 288 KB

A simple thread-safe, in-memory bus for Swift that supports fully-typed Events and States.

Home Page: https://dannys42.github.io/Causality/

License: Apache License 2.0

Swift 95.71% Ruby 4.29%
swift eventbus bus state-handlers state-management state events event-emitter pubsub notification-center

causality's Introduction

build status macOS iOS Linux Apache 2

Causality

Causality is a simple thread-safe, in-memory bus for Swift that supports fully-typed Events and States.

In addition, Causality has provisions for monitoring State information. State is similar to Event, but differ in that:

  • State handlers will be called immediately with the last known good value (if one is available)
  • State handlers will not be called if the state value is identical to the previous value
  • Whereas an Event has an associated Message, a State has an associated Value.
  • A state's Value must conform to the Equatable protocol.

Installation

Swift Package Manager

Add the Causality package to the dependencies within your application's Package.swift file. Substitute "x.y.z" with the latest Causality release.

.package(url: "https://github.com/dannys42/Causality.git", from: "x.y.z")

Add Causality to your target's dependencies:

.target(name: "example", dependencies: ["Causality"]),

Cocoapods

Add Causality to your Podfile:

pod `Causality`

Usage

Events

Just an event (no data)

The simplest event to manage has no associated data.

Declare Events

This declares an event called aTriggerEvent that has no associated data.

struct MyEvents {
    static let aTriggerEvent = Causality.Event<Causality.NoMessage>(label: "A Trigger")
}

Subscribe to events

To subscribe to this event:

let subscription = Causality.bus.subscribe(MyEvents.aTriggerEvent) { _ in
    print("Event was triggered")
}

Publish events

To publish/post an event of this type:

Causality.bus.publish(MyEvents.aTriggerEvent)

An event with associated data

Events can include data of any type (referred to as a Message). The event label is fully type specified with the message. So a subscriber will have a fully typed message available to its handler.

Define the Message

A message can be a standard Swift type like Int, String, etc. Or it can be a struct or class that conform to Causality.Message. Take care as to whether you want value or reference semantics for messages. Generally, value semantics (i.e. a struct) will be safer. In this example, we'll declare a struct:

struct InterestingMessage: Causality.Message {
    let string: String
    let number: Int
}

Declare the event

Events may be declared with an associated Message. If declared, the Message is a required typed parameter for publishing an event. And similarly it will be supplied as a typed parameter to subscribers of the event.

Declaring an event with a message:

let MyInterestingEvent = Causality.Event<SomeMessage>(label: "Some Event")
let MyStringEvent = Causality.Event<String>(label: "An event with a String message")
let MyNumberEvent = Causality.Event<Int>(label: "An event with an Int message")

Or categorize your events:

struct MyEvents {
    static let MyInterestingEvent = Causality.Event<InterestingMessage>(label: "An interesting Event 1")
    static let MyStringEvent = Causality.Event<String>(label: "An event with a String message")
    static let MyNumberEvent = Causality.Event<Int>(label: "An event with an Int message")
}

Subscribing and Unsubscribing to the events

Save your subscriptions to unsubscribe later:

let subscription = Causality.bus.subscribe(MyEvents.MyInterestingEvent) { interestingMessage in
    print("A message from MyInterestingEvent: \(interestingMessage)")
}

Casaulity.bus.unsubscribe(subscription)

Or unsubscribe from within a subscription handler. Here's an example of a one-shot event handler:

Causality.bus.subscribe(MyEvents.MyStringEvent) { subscription, string in
    print("A string from MyStringEvent: \(string)")
    
    subscription.unsubscribe()
}

Publish events

To publish/post an event:

Causality.bus.publish(MyEvents.MyInterestingEvent, 
    message: InterestingMessage(string: "Hello", number: 42))

Event Buses

Bus Alias

Create aliases for your bus:

let eventBus = Causality.bus

eventBus.publish(MyEvents.MyInterestingEvent, 
    message: InterestingMessage(string: "Hello", number: 42))

Local Buses

Or create local buses to isolate your events:

let newEventBus = Causality.Bus(label: "My local bus")

newEventBus.publish(MyEvents.interestingEvent, 
    message: InterestingMessage(string: "Hello", number: 42))

State

Define the State Value

Similar to an Event, a State has an associated Value. Values can be raw types such as Int, String, etc. Or they may be struct or a class. (Similar to a an event Message, you'll usually want to use a struct.) However a Value must conform to Equatable.

struct PlayerInfo: Causality.StateValue {
    let numberOfLives: Int
    let health: Int
    let armor: Int
}

Declare the state

Declaring a state with the associated value:

let playerState = Causality.State<PlayerInfo>(label: "Player State")

Or categorize your states:

struct GameStates {
    static let playerState1 = Causality.State<PlayerInfo>(label: "Player 1 State")
    static let playerState2 = Causality.State<PlayerInfo>(label: "Player 2 State")
}

Subscribing and Unsubscribing to State changes

Save your subscriptions to unsubscribe later:

let subscription = Causality.bus.subscribe(GameStates.playerState1) { state in
    print("Player 1 state changed to: \(state)")
}

Casaulity.bus.unsubscribe(subscription)

Or unsubscribe from within a subscription handler. This example will monitor only a single state change:

Causality.bus.subscribe(GameStates.playerState1) { subscription, message in
    print("Player 1 state changed to: \(state)")
    
    subscription.unsubscribe()
}

If the state was previously set, the subscription handler will be called immediately with the last known value. The subscription handler will only be called if subsequent .set() calls have differing values.

Setting State

Causality.bus.set(GameStates.playerState1, 
    value: PlayerInfo(numberOfLives: 3, health: 75, armor: 10))

Dynamic States

In the game example above, we have one Causality.State variable for every state. But what if we have "n" number of players? In that case, we can use Dynamic States. Dynamic States allows you to parameterize your State. Dynamic States are Codable and require you to define CodingKeys and to overload the encode() function to specify "key" parameters. These parameters are used to uniquely identify the state. For example:

class PlayerState<Value: PlayerInfo>: Causality.DynamicState<Value> {
    let playerId: Int

    init(playerId: Int) {
        self.playerId = playerId
    }
    enum CodingKeys: CodingKey {
        case playerId
    }
    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.playerId, forKey: .playerId)
    }
}

Now to subscribe:

Causality.bus.subscribe(PlayerState<PlayerInfo>(playerId: 1)) { subscription, playerInfo in
    print("Current player info is: \(playerInfo))
}

Or to add more organization:

struct GameState {
    static func playerState(_ playerId: Int) -> PlayerState<PlayerInfo> {
        return PlayerState<PlayerInfo>(playerId: playerId)
    }
}

Causality.bus.subscribe(GameState.playerState(1)) { subscription, playerInfo in
    print("Current player info is: \(playerInfo)")
}

And to set/update a state:

Causality.bus.set(state: GameState.playerState(1), 
                  value: PlayerInfo(
                            numberOfLines: 3,
                            health: 75,
                            armor: 100))

Similarly, you could use base types of Int, String, etc. for the Value.

let UserNameState = Causality.State<String>(label: "user name state")
Causality.bus.subscribe(UserNameState) { username in
    print("Username is now: \(username)")
}
Causality.bus.set(UserNameState, "Mary Jane Doe")

Dynamic Events

Events can be parameterized by defining them in a similar way:

class MyEvent<Message: Causality.Message>: Causality.DynamicEvent<Message> {

    let eventId: Int

    init(eventId: Int) {
        self.eventId = eventId
    }
    enum CodingKeys: CodingKey {
        case eventId
    }
    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.eventId, forKey: .eventId)
    }
}

Then create the event:

struct MyEvents {
    static func event(_ eventId: Int) -> MyEvent<InterestingMessage> {
        return MyEvent<InterestingMessage>(eventId: eventId)
    }
}

Subscribe to the event:

let subscription = Causality.bus.subscribe(MyEvents.event(1)) { subscription, message in
    print("A message from event \(subscription.event.eventId): \(message)")
}

And publish events:

Causality.bus.publish(MyEvents.event(1), 
    message: InterestingMessage(string: "Hello", number: 42))

API Documentation

For more information visit our API reference.

Related Projects

License

This library is licensed under Apache 2.0. The full license text is available in LICENSE.

causality's People

Contributors

dannys42 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.