-
-
Notifications
You must be signed in to change notification settings - Fork 1
201. Signals
In F-Mesh, the data exchanged between components is represented as signals — flexible units of information that can carry any type of payload. Signals serve as the foundation for communication within the mesh, enabling the dynamic flow of data between components.
Each signal consists of three main elements:
- Payload - The actual data being transferred (can be any Go type)
- Labels - Optional string key-value metadata for categorization and filtering
-
Scalars - Optional numeric key-value metadata (
string→float64)
To explore the full capabilities of signals, refer to the signal package API.
Signals are immutable. Mutating methods such as WithLabel, WithLabels, or MapPayload return a new signal; the original is never modified. This is important when signals are fanned out through pipes — multiple ports may share the same signal pointer, and immutability prevents one component from corrupting another's view of the data.
original := signal.New("hello")
tagged := original.WithLabel("source", "sensor1")
// original is unchanged
original.Labels().Has("source") // false
tagged.Labels().Has("source") // trueWhen you need to modify a signal that may be shared, always assign the result of a With* method:
sig = sig.WithLabel("processed", "true")The payload of a signal can be any valid Go data type, offering immense flexibility. This includes primitive types like integers and strings, complex types like structs and maps, or even nil.
It's important to note that every signal in F-Mesh always has a payload, even if that payload is nil. There is no concept of an "empty" signal, ensuring consistency in data handling across the mesh.
Example of creating a simple signal:
mySignal := signal.New("example payload") // A signal with a string payloadWhile individual signals are useful for most cases, there are scenarios where working with a group of signals simplifies the design. A signal group aggregates multiple signals, each potentially carrying a different type of payload.
// Create a group from multiple payloads
mySignals := signal.NewGroup(1, nil, 3, []int{4, 5, 6}, map[byte]byte{7: 8})
// Work with signals in a port (also a group)
port.Signals().Filter(signal.HasLabel("priority"))Signal groups are part of F-Mesh's consistent Collections and Groups API. For complete documentation on filtering, iterating, and working with groups, see the Collections and Groups page.
Signals can be tagged with labels - key-value pairs that provide metadata about the signal without affecting the payload. Labels are useful for categorization, filtering, routing, and debugging.
Because signals are copy-on-write, use With* methods to add labels:
// Create signal with labels
sig := signal.New("data").WithLabels(map[string]string{
"priority": "high",
"source": "sensor1",
})
// Or add labels one at a time
sig = sig.WithLabel("priority", "high").WithLabel("source", "sensor1")
// Query labels
if sig.Labels().ValueIs("priority", "high") {
// Handle high priority signal
}
// Filter signals by labels using predicate helpers
highPriority := port.Signals().Filter(signal.LabelEquals("priority", "high"))Labels are preserved when signals flow through pipes and can also be used with components and ports. For complete documentation on labels and scalars, see the Labels page.
Signals can also carry scalars — numeric metadata stored as float64 values. Scalars are useful for weights, priorities, timestamps, or any numeric annotation.
sig := signal.New("measurement").
WithScalar("weight", 0.85).
WithScalar("confidence", 0.97)
weight := sig.Scalars().GetOrDefault("weight", 0)Signal groups support cross-entity scalar aggregation (MinScalar, MaxScalar, AvgScalar, SumScalar) — see the Collections and Groups page.
The signal package provides composable predicate helpers for filtering:
port.Signals().Filter(signal.And(
signal.HasLabel("priority"),
signal.LabelEquals("priority", "high"),
))
port.Signals().Filter(signal.Or(
signal.LabelEquals("source", "sensor1"),
signal.LabelEquals("source", "sensor2"),
))Available helpers: Not, And, Or, HasLabel, LabelEquals, LabelContains, HasAllLabels, HasAnyLabel.
Signals support the same CoW pattern for scalars:
| Method | Description |
|---|---|
WithScalar(name, value) |
Add or update one scalar |
WithScalars(map) |
Add or update many |
WithOnlyScalars(map) |
Replace all scalars |
WithNoScalars() |
Remove all scalars |
WithoutScalars(names...) |
Remove specific scalars |
Beyond Filter and Map, groups support:
| Method | Description |
|---|---|
With(signals...) / WithPayloads(...) / Join(other)
|
Build new groups |
Contains(sig) / ContainsPayload(v)
|
Membership checks |
MapPayloads(mapper) / MapPayloadsIf(pred, mapper)
|
Transform payloads, preserve metadata |
Reduce(initial, fn) / ReducePayloads(initial, fn)
|
Fold group into single signal or value |
ForEachIf(pred, action) |
Iterate matching signals only |
Every(pred) / Any(pred) / Count(pred)
|
Predicate queries |
| Type | Signature | Used by |
|---|---|---|
Predicate |
func(*Signal) bool |
Filter, Any, Every
|
Mapper |
func(*Signal) *Signal |
Map, MapIf
|
PayloadMapper |
func(any) any |
MapPayload, MapPayloads
|
Reducer |
func(acc, s *Signal) *Signal |
Reduce |
PayloadReducer |
func(acc, payload any) any |
ReducePayloads |
| Error | When |
|---|---|
ErrNoSignalsInGroup |
FirstPayload() on empty group |
ErrPayloadNotComparable |
ContainsPayload with non-comparable type — use ContainsPayloadFunc instead |
Payload() always succeeds on a valid signal; FirstPayload() returns an error on empty groups.