Event Bus

Let's create an event bus that the entire application can interact with

Like many other programming languages, Vortex will cache module imports, meaning that files get evaluated only once on the first import, and the cached version will be returned on every consecutive import after that.

This allows us to instantiate a single object that can be imported and modified throughout the entire application.

Event Type

We'll begin by defining an Event type that the event bus can accept:

event_bus.vtx
type Event = (name) => ({
    name: name
})

It's pretty bare bones, but we'll use it to make sure that only valid events are passed to the event bus.

Event Bus

We'll now define the event bus constructor:

bus/event_bus.vtx
type EventBus = () => ({
    events: {},
    addEvent: (e) => {
        if (e.info().typename != "Event") {
            println(f"${e} is not a valid Event")
            return
        }

        if (this.events[e.name]) {
            println(f"Event ${e} is already defined")
            return
        }

        this.events[e.name] = {
            event: e,
            subscribers: []
        }
    },
    removeEvent: (event_name) => {
        this.events.remove_prop(event_name)
    },
    subscribe: (event_name, callback) => {
        if (!this.events[event_name]) {
            println(f"No such event \"${event_name}\"")
            return
        }

        this.events[event_name].subscribers.append(callback)
    },
    unsubscribe: (event_name, callback) => {
        if (!this.events[event_name]) {
            println(f"No such event \"${event_name}\"")
            return
        }

        for (this.events[event_name].subscribers, index, sub) {
            if (sub == callback) {
                this.events[event_name].subscribers.remove(index)
                break
            }
        }
    },
    emit: (event_name, payload = None) => {
        if (!this.events[event_name]) {
            println(f"No such event \"${event_name}\"")
            return
        }

        const e = this.events[event_name]
        for (e.subscribers, index, sub) {
            if (sub.info().arity == 0) {
                sub()
            } else {
                sub(payload)
            }
        }
    }
})

The event bus will need to have the ability to store events and their subscribers (really, just callbacks), add new events, remove events, subscribe and unsubscribe callbacks to events and emit events.

Just to make things nice and concise, we'll declare an EventType object that we'll use when subscribing/unsubscribing to events:

bus/event_bus.vtx
var EventType = {
    Funds_Added: "funds_added",
    Funds_Removed: "funds_removed"
}

Next, we instantiate a global event bus that will be used throughout the entire application, and add two new events to it based on the EventType we just declared:

bus/event_bus.vtx
const event_bus = EventBus()

event_bus.addEvent(Event(EventType.Funds_Added))
event_bus.addEvent(Event(EventType.Funds_Removed))

That's it, we now have an event bus that we can import in other parts of our application. This event bus has two events that can be subscribed to and emitted. Let's explore an example of that.

In another nested file, we'll import the event_bus and add a new event to it.

We'll also create some callbacks to handle the events and subscribe to them. This is all that this file will do, we'll handle emitting the events in another file.

bus/src/nested/other.vtx
import [event_bus, EventType, Event] : "../../event_bus"

EventType.Account_Deleted = "account_deleted"
event_bus.addEvent(Event(EventType.Account_Deleted))

var balance = 55.5

const handle_account_deleted = (payload) => {
    println("OH NOOOOO")
}

const handle_funds_added = (payload) => {
    balance += payload.amount
    println(balance)
}

const handle_funds_removed = (payload) => {
    balance -= payload.amount
    println(balance)
}

event_bus.subscribe(EventType.Funds_Added, handle_funds_added)
event_bus.subscribe(EventType.Funds_Removed, handle_funds_removed)
event_bus.subscribe(EventType.Account_Deleted, handle_account_deleted)

In our main file, we'll also import the other file we just wrote. This will evaluate it, and add the new event to the bus, as well as handle the subscriptions. We'll then import the same event_bus and emit those events:

bus/src/main.vtx
import other : "nested/other"
import [event_bus, EventType] : "../event_bus"

event_bus.emit(EventType.Funds_Added, { amount: 20 })
event_bus.emit(EventType.Funds_Removed, { amount: 13.46 })
event_bus.emit(EventType.Account_Deleted)

This should then go through the list of callbacks and fire them off, producing this output:

75.5
62.04
OH NOOOOO

Last updated