7.1.51. Delegates
This tutorial covers daslib/delegate — a type macro that generates multicast
callback structs from a function signature. A delegate holds a list of handlers
(lambdas or function pointers) that are all invoked when the delegate is called.
Think C# events/delegates.
Prerequisites: familiarity with lambdas, function pointers, and typedefs.
options gen2
options no_unused_function_arguments = false
require daslib/delegate
7.1.51.1. Declaring delegate types
Use typedef with the delegate() type macro. Pass either
function<...> or lambda<...> — both produce the same delegate type
(internally all handlers are stored as lambdas).
typedef OnDamage = delegate(type<function<(target : string; amount : int) : int>>)
typedef OnNotify = delegate(type<function<(msg : string) : void>>)
typedef OnTick = delegate(type<function<() : void>>)
7.1.51.2. Construction from lambdas
Pass a lambda directly to the constructor:
var del <- OnDamage(@(target : string; amount : int) : int {
print("hit {target} for {amount}\n")
return amount
})
let result = del.invoke("goblin", 10)
// output:
// hit goblin for 10
7.1.51.3. Construction from function pointers
Pass a function pointer with @@:
def apply_damage(target : string; amount : int) : int {
print("apply_damage({target}, {amount})\n")
return amount * 2
}
var del <- OnDamage(@@apply_damage)
let result = del.invoke("dragon", 25)
// output:
// apply_damage(dragon, 25)
// result = 50
The function pointer is automatically wrapped in a lambda internally.
7.1.51.4. Assignment (:=) — replace all handlers
The := operator clears the invocation list and sets a single handler:
var del <- OnDamage()
del := @(target : string; amount : int) : int {
print("handler A\n")
return amount
}
// length is 1
del := @(target : string; amount : int) : int {
print("handler B\n")
return amount * 3
}
// still length 1 — previous handler was replaced
You can also assign a function pointer: del := @@my_function.
7.1.51.5. Append (+=) — add handlers
The += operator appends a handler to the invocation list:
var del <- OnDamage()
del += @(target : string; amount : int) : int {
print("handler 1: {amount}\n")
return amount
}
del += @(target : string; amount : int) : int {
print("handler 2: {amount * 2}\n")
return amount * 2
}
del += @@apply_damage
// All handlers are called; the last handler's return value is returned
let result = del.invoke("orc", 10)
// output:
// handler 1: 10
// handler 2: 20
// apply_damage(orc, 10)
// result (from last handler) = 20
For non-void delegates, all handlers execute in order but only the last handler’s return value is returned.
7.1.51.6. Void delegates
Void delegates work the same way, but invoke returns nothing:
typedef OnNotify = delegate(type<function<(msg : string) : void>>)
var del <- OnNotify()
del += @(msg : string) {
print("log: {msg}\n")
}
del += @(msg : string) {
print("alert: {msg}\n")
}
del.invoke("server started")
// output:
// log: server started
// alert: server started
7.1.51.7. Utilities: empty, length, clear
var del <- OnTick()
del.empty() // true
del.length() // 0
del += @() { ... }
del += @() { ... }
del.empty() // false
del.length() // 2
del.clear()
del.empty() // true
7.1.51.8. Iterating handlers
Delegates support for-loop iteration via the each iterator:
var del <- OnNotify()
del += @(msg : string) { print("A\n") }
del += @(msg : string) { print("B\n") }
var count = 0
for (handler in del) {
count++
}
// count = 2
This gives read-only access to the underlying lambda list.
7.1.51.9. Practical example — event system
typedef OnPlayerHit = delegate(type<function<(player : string; damage : int) : int>>)
def armor_reduction(player : string; damage : int) : int {
let reduced = damage - 5
print("armor: {damage} -> {reduced}\n")
return reduced > 0 ? reduced : 0
}
def log_hit(player : string; damage : int) : int {
print("log: {player} took {damage} damage\n")
return damage
}
var on_hit <- OnPlayerHit()
on_hit += @@armor_reduction
on_hit += @@log_hit
let final_damage = on_hit.invoke("Hero", 12)
// output:
// armor: 12 -> 7
// log: Hero took 12 damage
// final_damage = 12
// Replace all handlers — god mode!
on_hit := @(player : string; damage : int) : int {
return 0
}
on_hit.invoke("Hero", 100) // returns 0
7.1.51.10. API summary
Operation |
Description |
|---|---|
|
Declare a delegate type from function signature |
|
Construct empty delegate |
|
Construct with one lambda handler |
|
Construct with one function pointer |
|
Clear and set single handler |
|
Append handler |
|
|
|
True if no handlers registered |
|
Number of registered handlers |
|
Remove all handlers |
|
Iterate over handlers |
See also
Full source: tutorials/language/51_delegate.das
Previous tutorial: Structure-of-Arrays (SOA)