Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mtdowling committed Dec 10, 2024
1 parent c977b20 commit 393690c
Show file tree
Hide file tree
Showing 13 changed files with 1,111 additions and 33 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: ci

on:
pull_request:
workflow_dispatch:
push:
branches: [main]

jobs:
Test:
strategy:
matrix:
lua-version: ["5.4", "5.3", "5.2", "5.1", "luajit"]
os: ["ubuntu-latest"]
include:
- os: "macos-latest"
lua-version: "5.4"
# - os: "windows-latest"
# lua-version: "luajit"
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@master
- uses: hishamhm/gh-actions-lua@master
with:
luaVersion: ${{ matrix.lua-version }}
- uses: hishamhm/gh-actions-luarocks@master
- name: Test
run: |
make dev
make
34 changes: 1 addition & 33 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,4 @@ luac.out
*.zip
*.tar.gz

# Object files
*.o
*.os
*.ko
*.obj
*.elf

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

build/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changelog

All notable changes to this project will be documented in this file.

## [0.1.0] - 2024-12-10

### Added

Initial release.
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.PHONY: dev build gen check test

VERSION=0.1.0

build: compile check test build

dev:
luarocks install cyan

compile:
cyan build
luarocks build

check: compile
tl check -q spec/integ.tl
luacheck src/emitter.lua

test: compile
luarocks test

newrock:
luarocks new_version --dir rockspecs --tag=v$(VERSION) emitter.tl-dev-1.rockspec $(VERSION)
221 changes: 221 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# emitter.tl

A small, strongly typed event emitter library for Lua and typed with
[Teal](https://github.com/teal-language/tl).

## Key features

* Designed around strongly typed events using Teal. Events are no longer just
strings and bags of data.
* Allows for decoupling logic by subscribing and unsubscribing to events.
* Support for receiving an event at most once.
* Can efficiently forward all events from an Emitter to another Emitter.
This is great for things like games that have global events and listeners
that should only persist for the lifetime of a specific emitter.
* Listeners can be added or removed while emitting, and removals take effect
immediately.

## Usage

First, require the module:

```teal
local Emitter = require("emitter")
```

### Creating an Emitter

An `Emitter` is used to publish and subscribe to events.

```teal
local emitter = Emitter.new()
```

### Creating an Event

Events are strongly typed and require a dedicated type for each kind of event.

For reference, this is the type definition of Event:

```teal
local interface Event
type: Event
end
```

Each record that implements an Event must define a `type` property of the same
type as the event.

```teal
local record WarningEvent is Emitter.Event
message: string
end
```

This library doesn't have any requirements on how instances of events are
created.

An instance of a `WarningEvent` can be created using basic table syntax:

```teal
local e: WarningEvent = {type = WarningEvent, message = "This is a warning"}
```

Alternatively, you can provide a constructor for your events.

```teal
function WarningEvent.new(message: string): WarningEvent
return {type = WarningEvent, message = message}
end
local e: WarningEvent = WarningEvent.new("This is a warning")
```

This event can now be emitted and subscribed to.

### Subscribing to an Event

Listener functions are subscribed to an event using
`on<E is Event>(emitter: Emitter, E: event, function(E), config?: ListenerConfig)`.

```teal
emitter:on(WarningEvent, function(event: WarningEvent)
print(event.message)
end)
```

### Emitting an Event

Events are emitted using `emit(Event: event)`.

```teal
emitter:emit(WarningEvent.new("This is a warning"))
```

Emitting the event will print "This is a warning".

### Event listener configuration

An optional configuration record can be passed when subscribing to an event
when using `on` or `once`:

* `id: string`: An identifier for the listener (defaults to ""). An identifier
can be used to unsubscribe the listener by ID. No uniqueness checks are
performed on the ID; multiple listeners can use the same ID, allowing events
to be grouped.
* `position: "first" | "last"`: Controls whether the listener is added as the
last listener for the event using "last" (the default) or the first listener
for the event using "first".

### Unsubscribing from an Event

`off<E is Event>(event: E, listener: Listener<E> | string)` is used to
stop a listener from receiving events.

You can pass in the function that was used to subscribe to the event:

```teal
local function onWarning(event: WarningEvent)
print(event.message)
end
emitter:on(WarningEvent, onWarning)
-- Unsubscribe the function.
emitter:off(WarningEvent, onWarning)
```

When subscribing to an event, an identifier can be given to the listener
so that the listener can be unsubscribed by ID rather than the actual function.

```teal
emitter:on(WarningEvent, onWarning, { id = "warning" })
emitter:off(WarningEvent, "warning")
```

IDs can be used for grouping event listeners.

```teal
-- While adding listeners, use the same ID to group them.
emitter:on(WarningEvent, b, { id = "print-group" })
emitter:on(WarningEvent, b, { id = "print-group" })
-- Remove both a and b listeners because they both have the id "print-group".
emitter:off(WarningEvent, "print-group")
```

### Receiving an event at most once

The `once<E is Event>(emitter: Emitter, E: event, function(E))` method can be
used to subscribe to an event and have the listeners automatically removed
after the first event is received.

```teal
emitter:once(WarningEvent, function(event: WarningEvent)
print("Once: " .. event.message)
end)
```

### Forwarding events

Emitters can forward all events to another Emitter, allowing for a fan-out
pattern. This is done using `startForwarding(emitter: Emitter)`:

```teal
local forwardEmitter = Emitter.new()
forwardEmitter:on(WarningEvent, function(event: WarningEvent)
print("Child: " .. event.message)
end)
emitter:startForwarding(forwardEmitter)
emitter:emit(WarningEvent.new("This is a warning"))
```

Will output:

```text
This is a warning
Child: This is a warning
```

An emitter can be detached from another emitter using
`stopForwarding(emitter: Emitter)`:

```teal
emitter:stopForwarding(forwardEmitter)
```

## Clearing and resetting an Emitter

`removeAllListeners(event: Event)` is used to remove all listeners for
an event type:

```teal
emitter:removeAllListeners(WarningEvent)
```

`reset()` is used to unsubscribe all listeners and stop forwarding all events:

```teal
emitter:reset()
```

## Installation

You can copy and paste `src/emitter.tl` or `src/emitter.lua` into your project.

## Contributing

The source code is written in Teal and compiled to Lua. The updated and
compiled Lua must be part of every code change to the Teal source code.
You can compile Teal to Lua and run tests using:

```sh
make
```

## License

This module is free software; you can redistribute it and/or modify it under
the terms of the MIT license. See LICENSE for details.
48 changes: 48 additions & 0 deletions emitter.tl-dev-1.rockspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
rockspec_format = "3.0"
package = "emitter.tl"
version = "dev-1"
source = {
url = "git+https://github.com/mtdowling/emitter.tl.git",
branch = "main",
}
description = {
summary = "A small, strongly typed event emitter library for Lua and typed with Teal",
detailed = [[
* Designed around strongly typed events using Teal. Events are no longer just
strings and bags of data.
* Allows for decoupling logic by subscribing and unsubscribing to events.
* Support for receiving an event at most once.
* Can efficiently forward all events from an Emitter to another Emitter.
This is great for things like games that have global events and listeners
that should only persist for the lifetime of a specific emitter.
* Listeners can be added or removed while emitting, and removals take effect
immediately.
]],
homepage = "https://github.com/mtdowling/emitter.tl",
license = "MIT <http://opensource.org/licenses/MIT>",
}
dependencies = {
"lua >= 5.1",
}
test_dependencies = {
"busted",
}
build_dependencies = {
"tl >= 0.24.1",
"cyan >= 0.4.0",
"luacheck >= 0.2.0",
}
test = {
type = "busted",
}
build = {
type = "builtin",
modules = {
emitter = "src/emitter.lua",
},
install = {
lua = {
emitter = "src/emitter.tl",
},
},
}
Loading

0 comments on commit 393690c

Please sign in to comment.