Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V.0.1.0 #86

Merged
merged 41 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
021bd14
Update README.md
hovsep Nov 10, 2024
d834118
Waiting for inputs: add tests
hovsep Oct 4, 2024
5a84753
Minor: comment fixed
hovsep Oct 7, 2024
7a81503
Extract common name property
hovsep Oct 9, 2024
486c11c
Extract common description property
hovsep Oct 9, 2024
1222a6e
Minor: refactor traits
hovsep Oct 9, 2024
a5fc1a1
New trait: labeled entity
hovsep Oct 9, 2024
740b0d1
Add labels to ports
hovsep Oct 9, 2024
c43b153
Add labels to components
hovsep Oct 9, 2024
8251f16
LabeledEntity: add method
hovsep Oct 9, 2024
de0c6cd
WIP: dot exporter
hovsep Oct 8, 2024
aa7dc91
Dot exporter initial code
hovsep Oct 9, 2024
466a9e3
Dot exporter refactored
hovsep Oct 10, 2024
f195155
[WIP] Export with cycles
hovsep Oct 11, 2024
706e6bc
Cycle refactored: add number field
hovsep Oct 12, 2024
7de1c4f
Dot exporter: add config
hovsep Oct 12, 2024
ceea8c0
[WIP] Add chainable trait to signal and signal group
hovsep Oct 15, 2024
51829a3
[WIP] Increase coverage
hovsep Oct 15, 2024
2f61c28
Rename port.signals to buffer
hovsep Oct 15, 2024
08f674d
[WIP] added chainable to port and port group
hovsep Oct 15, 2024
5a8fd68
[WIP] added chainable to component
hovsep Oct 17, 2024
4433f91
Add some shortcut methods
hovsep Oct 20, 2024
72e02b3
Make fmesh chainable
hovsep Oct 20, 2024
8048cca
Minor: refactor receiver names
hovsep Oct 23, 2024
46a4a63
Make Cycle chainable
hovsep Oct 23, 2024
d8693a3
Propagate chain error to activation cycle
hovsep Oct 28, 2024
e66e41b
Increase coverage
hovsep Oct 29, 2024
34c1a7f
Return chainable with error instead of separate error when possible
hovsep Oct 29, 2024
a0071b5
Extract Ports type
hovsep Nov 1, 2024
7726159
Label port with direction and validate pipes using those labels
hovsep Nov 2, 2024
9210d95
Chainable API: polish error handling
hovsep Nov 4, 2024
e745e14
Rename activation result error to avoid confusion with chain error (o…
hovsep Nov 4, 2024
c147585
Update readme
hovsep Nov 4, 2024
e53ebd0
Rename chainable error to be more go idiomatic
hovsep Nov 4, 2024
87cddc3
Bugfix: fix looped components
hovsep Nov 4, 2024
9f9ecbe
Add fibonacci example
hovsep Nov 4, 2024
457c75f
Remove experiments which are not worth converting to examples
hovsep Nov 4, 2024
5ecbc29
Refactor drain logic: clear all inputs and then flush all
hovsep Nov 8, 2024
610b64b
Fix panic in example
hovsep Nov 9, 2024
8efe078
Add example
hovsep Nov 10, 2024
a8ca31c
Add readme example
hovsep Nov 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 22 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

<div align="center">
<img src="./assets/img/logo.png" width="200" height="200" alt="f-mesh"/>
<h1>f-mesh</h1>
Expand All @@ -7,66 +6,58 @@
</div>

<h1>What is it?</h1>
<p>F-Mesh is a simplistic FBP-inspired framework in Go.
It allows you to express your program as a mesh of interconnected components.
You can think of it as a simple functions orchestrator.
<p>F-Mesh is a functions orchestrator inspired by FBP.
It allows you to express your program as a mesh of interconnected components (or more formally as a computational graph).
</p>
<h3>Main concepts:</h3>
<ul>
<li>F-Mesh consists of multiple <b>Components</b> - the main building blocks</li>
<li>Components have unlimited number of input and output <b>Ports</b></li>
<li>The main job of each component is to read inputs and provide outputs</li>
<li>Any output port can be connected to any input port via <b>Pipes</b></li>
<li>The component behaviour is defined by its <b>Activation function</b></li>
<li>The framework checks when components are ready to be activated and calls their activation functions concurrently</li>
<li>One such iteration is called <b>Activation cycle</b></li>
<li>On each activation cycle the framework does same things: activates all the components ready for activation, flushes the data through pipes and disposes input <b>Signals (the data chunks flowing between components)</b></li>
<li>Ports and pipes are type agnostic, any data can be transferred or aggregated on any port</li>
<li>The framework works in discrete time, not it wall time. The quant of time is 1 activation cycle, which gives you "logical parallelism" out of the box</li>
<li>F-Mesh is suitable for logical wireframing, simulation, functional-style computations and implementing simple concurrency patterns without using the concurrency primitives like channels or any sort of locks</li>
<li>Ports and pipes are type agnostic, any data can be transferred to any port</li>
<li>The framework works in discrete time, not it wall time. The quant of time is 1 activation cycle, which gives you "logical parallelism" out of the box (activation function is running in "frozen time")</li>
</ul>

<h1>What it is not?</h1>
<p>F-mesh is not a classical FBP implementation, and it is not fully async. It does not support long-running components or wall-time events (like timers and tickers)</p>
<p>The framework is not suitable for implementing complex concurrent systems</p>
<p>F-mesh is not a classical FBP implementation, it does not support long-running components or wall-time events (like timers and tickers)</p>


<h2>Example:</h2>

```go
// Create f-mesh
fm := fmesh.New("hello world").
fm := fmesh.New("hello world").
WithComponents(
component.New("concat").
WithInputs("i1", "i2").
WithOutputs("res").
WithActivationFunc(func(inputs port.Collection, outputs port.Collection) error {
word1 := inputs.ByName("i1").Signals().FirstPayload().(string)
word2 := inputs.ByName("i2").Signals().FirstPayload().(string)
WithActivationFunc(func(inputs *port.Collection, outputs *port.Collection) error {
word1 := inputs.ByName("i1").FirstSignalPayloadOrDefault("").(string)
word2 := inputs.ByName("i2").FirstSignalPayloadOrDefault("").(string)

outputs.ByName("res").PutSignals(signal.New(word1 + word2))
return nil
}),
component.New("case").
WithInputs("i1").
WithOutputs("res").
WithActivationFunc(func(inputs port.Collection, outputs port.Collection) error {
inputString := inputs.ByName("i1").Signals().FirstPayload().(string)
WithActivationFunc(func(inputs *port.Collection, outputs *port.Collection) error {
inputString := inputs.ByName("i1").FirstSignalPayloadOrDefault("").(string)

outputs.ByName("res").PutSignals(signal.New(strings.ToTitle(inputString)))
return nil
})).
.WithConfig(fmesh.Config{
ErrorHandlingStrategy: fmesh.StopOnFirstErrorOrPanic,
CyclesLimit: 10,
})
WithConfig(fmesh.Config{
ErrorHandlingStrategy: fmesh.StopOnFirstErrorOrPanic,
CyclesLimit: 10,
})

fm.Components().ByName("concat").Outputs().ByName("res").PipeTo(
fm.Components().ByName("case").Inputs().ByName("i1"),
)

// Init inputs
fm.Components().ByName("concat").Inputs().ByName("i1").PutSignals(signal.New("hello "))
fm.Components().ByName("concat").Inputs().ByName("i2").PutSignals(signal.New("world !"))
fm.Components().ByName("concat").InputByName("i1").PutSignals(signal.New("hello "))
fm.Components().ByName("concat").InputByName("i2").PutSignals(signal.New("world !"))

// Run the mesh
_, err := fm.Run()
Expand All @@ -78,8 +69,8 @@ You can think of it as a simple functions orchestrator.
}

//Extract results
results := fm.Components().ByName("case").Outputs().ByName("res").Signals().FirstPayload()
fmt.Printf("Result is :%v", results)
results := fm.Components().ByName("case").OutputByName("res").FirstSignalPayloadOrNil()
fmt.Printf("Result is : %v", results)
```

<h2>Version 0.1.0 coming soon</h2>
See more in ```examples``` directory.
<h2>Version 0.1.0 coming soon</h2>
25 changes: 25 additions & 0 deletions common/chainable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package common

type Chainable struct {
err error
}

// NewChainable initialises new chainable
func NewChainable() *Chainable {
return &Chainable{}
}

// SetErr sets chainable error
func (c *Chainable) SetErr(err error) {
c.err = err
}

// HasErr returns true when chainable has error
func (c *Chainable) HasErr() bool {
return c.err != nil
}

// Err returns chainable error
func (c *Chainable) Err() error {
return c.err
}
15 changes: 15 additions & 0 deletions common/described_entity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package common

type DescribedEntity struct {
description string
}

// NewDescribedEntity constructor
func NewDescribedEntity(description string) DescribedEntity {
return DescribedEntity{description: description}
}

// Description getter
func (e DescribedEntity) Description() string {
return e.description
}
65 changes: 65 additions & 0 deletions common/described_entity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package common

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestNewDescribedEntity(t *testing.T) {
type args struct {
description string
}
tests := []struct {
name string
args args
want DescribedEntity
}{
{
name: "empty description",
args: args{
description: "",
},
want: DescribedEntity{
description: "",
},
},
{
name: "with description",
args: args{
description: "component1 is used to generate logs",
},
want: DescribedEntity{
description: "component1 is used to generate logs",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, NewDescribedEntity(tt.args.description))
})
}
}

func TestDescribedEntity_Description(t *testing.T) {
tests := []struct {
name string
describedEntity DescribedEntity
want string
}{
{
name: "empty description",
describedEntity: NewDescribedEntity(""),
want: "",
},
{
name: "with description",
describedEntity: NewDescribedEntity("component2 is used to handle errors"),
want: "component2 is used to handle errors",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tt.describedEntity.Description())
})
}
}
97 changes: 97 additions & 0 deletions common/labeled_entity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package common

import (
"errors"
"fmt"
)

type LabelsCollection map[string]string

type LabeledEntity struct {
labels LabelsCollection
}

var (
ErrLabelNotFound = errors.New("label not found")
)

// NewLabeledEntity constructor
func NewLabeledEntity(labels LabelsCollection) LabeledEntity {
return LabeledEntity{labels: labels}
}

// Labels getter
func (e *LabeledEntity) Labels() LabelsCollection {
return e.labels
}

// Label returns the value of single label or nil if it is not found
func (e *LabeledEntity) Label(label string) (string, error) {
value, ok := e.labels[label]

if !ok {
return "", fmt.Errorf("label %s not found, %w", label, ErrLabelNotFound)
}

return value, nil
}

// LabelOrDefault returns label value or default value in case of any error
func (e *LabeledEntity) LabelOrDefault(label string, defaultValue string) string {
value, err := e.Label(label)
if err != nil {
return defaultValue
}
return value
}

// SetLabels overwrites labels collection
func (e *LabeledEntity) SetLabels(labels LabelsCollection) {
e.labels = labels
}

// AddLabel adds or updates(if label already exists) single label
func (e *LabeledEntity) AddLabel(label string, value string) {
if e.labels == nil {
e.labels = make(LabelsCollection)
}
e.labels[label] = value
}

// AddLabels adds or updates(if label already exists) multiple labels
func (e *LabeledEntity) AddLabels(labels LabelsCollection) {
for label, value := range labels {
e.AddLabel(label, value)
}
}

// DeleteLabel deletes given label
func (e *LabeledEntity) DeleteLabel(label string) {
delete(e.labels, label)
}

// HasLabel returns true when entity has given label or false otherwise
func (e *LabeledEntity) HasLabel(label string) bool {
_, ok := e.labels[label]
return ok
}

// HasAllLabels checks if entity has all labels
func (e *LabeledEntity) HasAllLabels(label ...string) bool {
for _, l := range label {
if !e.HasLabel(l) {
return false
}
}
return true
}

// HasAnyLabel checks if entity has at least one of given labels
func (e *LabeledEntity) HasAnyLabel(label ...string) bool {
for _, l := range label {
if e.HasLabel(l) {
return true
}
}
return false
}
Loading
Loading