A lightweight, type-safe signal/slot implementation for the Odin programming language. This library provides a simple and efficient way to implement the observer pattern, allowing components to communicate through loosely coupled events.
- Type Safety: Generic implementation ensures compile-time type checking
- Context Support: Optional context parameter for additional data in callbacks
- Automatic Cleanup: Built-in cleanup functions for resource management
- Memory Management: Proper allocation and deallocation of signal instances
- Simple API: Easy-to-use connect/disconnect/emit interface
import signals "path/to/signals"
// Create a signal that emits strings
signal := signals.init(string)
// Connect a listener
listener_id := signals.connect(signal, nil, proc(payload: signals.Signal_Payload(string, rawptr)) {
fmt.printf("Received: %s\n", payload.value)
})
// Emit a value
signals.emit(signal, "Hello, World!")
// Clean up
signals.deinit(signal)Signal_Id :: int
Signal :: struct($T, $C: typeid) {
listeners: map[int](Signal_Listener(T, C)),
_last_id: Signal_Id,
}Creates a signal with a specific value type and rawptr context.
init_with_value :: proc($T: typeid) -> ^Signal(T, rawptr)Creates a signal with both value and context types.
init_with_value_and_ctx :: proc($T, $C: typeid) -> ^Signal(T, C)Generic procedure that can be called with either one or two type parameters.
Connects a listener with a simple callback function.
connect_simple :: proc(
self: ^Signal($T, $C),
ctx: C,
fn: proc(payload: Signal_Payload(T, C)),
) -> Signal_IdConnects a listener with an optional cleanup function.
connect_with_cleanup :: proc(
self: ^Signal($T, $C),
ctx: C,
fn: proc(payload: Signal_Payload(T, C)),
cleanup: proc(ctx: C),
) -> Signal_IdGeneric procedure that can be called with or without cleanup.
Emits a value to all connected listeners.
emit :: proc(self: ^Signal($T, $C), value: T)Disconnects a listener by its ID and calls cleanup if provided.
disconnect :: proc(self: ^Signal($T, $C), id: Signal_Id)Properly cleans up a signal instance, disconnecting all listeners.
deinit :: proc(self: ^Signal($T, $C))import signals "path/to/signals"
import "core:fmt"
main :: proc() {
// Create a signal that emits integers
counter_signal := signals.init(int)
defer signals.deinit(counter_signal)
// Connect multiple listeners
id1 := signals.connect(counter_signal, nil, proc(payload: signals.Signal_Payload(int, rawptr)) {
fmt.printf("Listener 1: Count is %d\n", payload.value)
})
id2 := signals.connect(counter_signal, nil, proc(payload: signals.Signal_Payload(int, rawptr)) {
fmt.printf("Listener 2: Count is %d\n", payload.value)
})
// Emit values
signals.emit(counter_signal, 42)
signals.emit(counter_signal, 100)
// Disconnect one listener
signals.disconnect(counter_signal, id1)
// Emit again - only listener 2 will receive
signals.emit(counter_signal, 200)
}import signals "path/to/signals"
import "core:fmt"
User :: struct {
name: string,
age: int,
}
main :: proc() {
// Create a signal with User context
user_signal := signals.init_with_value_and_ctx(string, User)
defer signals.deinit(user_signal)
user := User{name = "Alice", age = 30}
// Connect with context
signals.connect(user_signal, user, proc(payload: signals.Signal_Payload(string, User)) {
fmt.printf("User %s (age %d) says: %s\n",
payload.ctx.name, payload.ctx.age, payload.value)
})
signals.emit(user_signal, "Hello from context!")
}import signals "path/to/signals"
import "core:fmt"
main :: proc() {
signal := signals.init(string)
defer signals.deinit(signal)
// Connect with cleanup function
signals.connect_with_cleanup(
signal,
nil,
proc(payload: signals.Signal_Payload(string, rawptr)) {
fmt.printf("Received: %s\n", payload.value)
},
proc(ctx: rawptr) {
fmt.println("Cleanup called!")
},
)
signals.emit(signal, "Test message")
// Cleanup will be called when disconnecting or deinitializing
}The library handles memory management automatically:
initprocedures allocate memory for the signaldeinitproperly cleans up all listeners and frees the signal memorydisconnectcalls cleanup functions if provided- Always call
deinitwhen you're done with a signal to prevent memory leaks
This implementation is not thread-safe. If you need thread-safe signals, you'll need to add appropriate synchronization mechanisms around the signal operations.
- Signal emission is O(n) where n is the number of connected listeners
- Connection and disconnection are O(1) operations
- Memory overhead is minimal with efficient map-based storage