Home > Software engineering >  Observer pattern or a Publish/Subscribe pattern for a cellular automaton
Observer pattern or a Publish/Subscribe pattern for a cellular automaton

Time:11-15

I am trying to code an observer pattern or a publish/submit pattern for a sort of cellular automaton.

The classical observer pattern does not to the trick because if a cell A subscribes to changes in a cell B and vice-versa, the application will run out of stack owing to the recursive approach (B.update() will call A.update() and so on and the app will run out of stack).

So I thought about using a publish/subscribe pattern where respective cells pass each other messages, rather than calling each other's update() methods.

Here is a simple example with two cells A and B:

package main

import (
    "fmt"
    ps "publish/pubsub"
)

func main() {

    fmt.Printf("Starting\n")

    chEnd := make(chan int)

    // initialize
    a := ps.NewNode(1, 0)
    b := ps.NewNode(2, 0)

    // connect nodes
    a.Connect(b.ChOut)
    b.Connect(a.ChOut)

    // Start listening
    a.Listen()
    b.Listen()

    // Start sending data on one arbitrary node
    // to start the process.
    a.ChIn <- 10

    <-chEnd
}

and the corresponding lib

package pubsub

import (
    "fmt"
)

type Node struct {
    Id    int
    State int
    ChOut chan int
    ChIn  chan int
}

func NewNode(id int, state int) Node {
    chout := make(chan int)
    var chin chan int
    return Node{id, state, chout, chin}
}

func (p *Node) Broadcast(inItem int) {
    p.ChOut <- inItem   1
    //time.Sleep(100 * time.Millisecond)
}

func (p *Node) Listen() {
    go func() {
        for {
            select {
            case inItem := <-p.ChIn:
                fmt.Printf("%d: %d\n", p.Id, inItem)
                p.Broadcast(inItem)
            }
        }
    }()
}

func (p *Node) Connect(ch chan int) {
    p.ChIn = ch
}

Each node has a input and an output channe. The input channel of B is the output channel of A and vice-versa.

Every update consists merely of incrementing the data passed by the other cell.

It seems to work. So far, so good.

I tried to do something similar with a set of 4 cells A, B, C, D, in order to simulate a one dimensional cellular automaton of sorts.

In this second attempt, each cell has two input channels (let and right) to listen to its closest left- and right-hand neighbour, respectively (ChinL and ChinR). each cell has to output channels to communicate its latest updated state to its closest neighbours (ChoutL and ChoutR). I must have done something wrong in the implementation of that scheme with 4 cells, because it yields odd results : the values passed back and forth between the 4 cells seem to hit a threshold instead of increasing at every consecutive step: here is the code:

package main

import (
    "fmt"
    ps "publish/pubsub"
)

func main() {

    fmt.Printf("Starting\n")

    chEnd := make(chan int)

    // initialize
    a := ps.NewNode(1, 0)
    b := ps.NewNode(2, 0)
    c := ps.NewNode(3, 0)
    d := ps.NewNode(4, 0)

    // connect nodes
    a.ChInL = d.ChOutR
    a.ChInR = b.ChOutL

    b.ChInL = a.ChOutR
    b.ChInR = c.ChOutL

    c.ChInL = b.ChOutR
    c.ChInR = d.ChOutL

    d.ChInL = c.ChOutR
    d.ChInR = a.ChOutL

    // Start listening
    go a.Listen()
    go b.Listen()
    go c.Listen()
    go d.Listen()

    go a.Broadcast()
    go b.Broadcast()
    go c.Broadcast()
    go d.Broadcast()

    // Start sending data on one arbitrary node
    // to start the process.
    a.ChInL <- 1

    // Dummy read on channel to make main() wait
    <-chEnd
}

/*
    A   B   C   D
    LR  LR  LR  LR
*/

and the corresponding lib


package pubsub

import (
    "fmt"
    "strings"
)

type Node struct {
    Id     int
    State  int
    ChOutL chan int
    ChOutR chan int
    ChInL  chan int
    ChInR  chan int
    ChIO   chan int
}

func NewNode(id int, state int) Node {
    choutL := make(chan int)
    choutR := make(chan int)
    var chinL chan int
    var chinR chan int
    chIO := make(chan int)
    return Node{id, state, choutL, choutR, chinL, chinR, chIO}
}

func (p *Node) Broadcast() {
    for item := range p.ChIO {
        p.ChOutL <- item   1
        p.ChOutR <- item   1
        fmt.Printf("%d: %d %s\n", p.Id, item, strings.Repeat("*", item))
    }
}

func (p *Node) Listen() {
    for {
        //time.Sleep(100 * time.Millisecond)
        select {
        case inItem := <-p.ChInL:
            go func() {
                p.ChIO <- inItem
            }()
        case inItem := <-p.ChInR:
            go func() {
                p.ChIO <- inItem
            }()
        }
    }
}

For completeness, here is the go.mod for the modules above:

module publish

go 1.17

CodePudding user response:

For every signal a node receives on p.ChInL or p.ChInR you send out 2 signals. The first to p.ChOutL and the second to p.ChOutR. Since every node does this there will be an exponential amount of signals going around.

When running it locally the threshold is between 20 and 25. So about 2^25 33554432 signals going around. To see 26, the program will need to process 67108864 signals. So it will go past this threshold, just exponentially slower.

Now for the fix. I think you should implement some sort of tick system. So instead of sending update signals for every change to the automaton you send one update every tick like 20 times per second.

Maybe better yet, instead of using routines and channels, just make an slice of nodes and loop over them (again only updating the neighbor and not propagating it in the same tick/loop). Each iteration of the loop the state of all nodes has changed. I believe this is also how other automatons like game-of-live work. Now you can run the simulation as fast as your computer can run.

CodePudding user response:

Here is one possible alternative:

package pubsub

import (
    "fmt"
    "math/rand"
    "strings"
    "time"
)

type Node struct {
    Id     int
    State  int
    ChOutL chan int
    ChOutR chan int
    ChInL  chan int
    ChInR  chan int
    ChIO   chan int
}

func NewNode(id int, state int) Node {
    choutL := make(chan int)
    choutR := make(chan int)
    var chinL chan int
    var chinR chan int
    chIO := make(chan int)
    return Node{id, state, choutL, choutR, chinL, chinR, chIO}
}

func (p *Node) Broadcast() {
    for item := range p.ChIO {
        rnd := rand.Intn(2)
        if rnd == 0 {
            p.ChOutL <- item   1
        } else {
            p.ChOutR <- item   1
        }
        fmt.Printf("%d: %d %s\n", p.Id, item, strings.Repeat("*", item))
    }
}

func (p *Node) Listen() {
    for {
        time.Sleep(100 * time.Millisecond)
        select {
        case inItem := <-p.ChInL:
            p.ChIO <- inItem
        case inItem := <-p.ChInR:
            p.ChIO <- inItem
        }
    }
}

CodePudding user response:

Here is a second alternative:

package main

import (
    "fmt"
    ps "pub/pubsub"
)

func main() {

    fmt.Printf("Hello\n")

    chEnd := make(chan int)

    A := ps.NewNode(1, 0)
    B := ps.NewNode(2, 0)
    C := ps.NewNode(3, 0)
    D := ps.NewNode(4, 0)

    B.LeftNode = &A
    B.RightNode = &C
    C.LeftNode = &B
    C.RightNode = &D
    D.LeftNode = &C
    D.RightNode = &A
    A.LeftNode = &D
    A.RightNode = &B

    A.Listen()
    B.Listen()
    C.Listen()
    D.Listen()

    A.State = 1
    <-chEnd

}

//----


package pubsub

import (
    "fmt"
    "strings"
    "sync"
    "time"
)

type Node struct {
    Id         int
    State      int
    LeftNode   *Node
    RightNode  *Node
    LeftState  int
    RightState int
}

var m sync.Mutex

func NewNode(id int, state int) Node {
    return Node{id, state, nil, nil, 0, 0}
}

func (n *Node) Listen() {
    go func() {
        for {
            m.Lock()
            time.Sleep(10 * time.Millisecond)
            if n.LeftState != n.LeftNode.State {
                n.LeftState = n.LeftNode.State
                n.State = n.LeftNode.State   1
                fmt.Printf("%d: %d %s\n", n.Id, n.State, strings.Repeat("*", n.State))
            }
            m.Unlock()
        }
    }()
    go func() {
        for {
            m.Lock()
            time.Sleep(10 * time.Millisecond)
            if n.RightState != n.RightNode.State {
                n.RightState = n.RightNode.State
                n.State = n.RightNode.State   1
                fmt.Printf("%d: %d %s\n", n.Id, n.State, strings.Repeat("*", n.State))
            }
            m.Unlock()
        }
    }()
}



  • Related