mirror of https://github.com/nealey/vail.git
Channels work now I think
This commit is contained in:
parent
d5f6038670
commit
cdf3869454
|
@ -0,0 +1,92 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Book struct {
|
||||||
|
entries map[string]*Repeater
|
||||||
|
events chan bookEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func NewBook() Book {
|
||||||
|
return Book{
|
||||||
|
entries: make(map[string]*Repeater),
|
||||||
|
events: make(chan bookEvent, 5),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bookEventType int
|
||||||
|
const (
|
||||||
|
joinEvent = bookEventType(iota)
|
||||||
|
partEvent
|
||||||
|
sendEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
type bookEvent struct {
|
||||||
|
eventType bookEventType
|
||||||
|
name string
|
||||||
|
w io.Writer
|
||||||
|
p []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Book) Join(name string, w io.Writer) {
|
||||||
|
b.events <- bookEvent{
|
||||||
|
eventType: joinEvent,
|
||||||
|
name: name,
|
||||||
|
w: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Book) Part(name string, w io.Writer) {
|
||||||
|
b.events <- bookEvent{
|
||||||
|
eventType: partEvent,
|
||||||
|
name: name,
|
||||||
|
w: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Book) Send(name string, p []byte) {
|
||||||
|
b.events <- bookEvent{
|
||||||
|
eventType: sendEvent,
|
||||||
|
name: name,
|
||||||
|
p: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Book) Run() {
|
||||||
|
for {
|
||||||
|
b.loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Book) loop() {
|
||||||
|
event := <-b.events
|
||||||
|
repeater, ok := b.entries[event.name]
|
||||||
|
|
||||||
|
switch event.eventType {
|
||||||
|
case joinEvent:
|
||||||
|
if ! ok {
|
||||||
|
repeater = NewRepeater()
|
||||||
|
b.entries[event.name] = repeater
|
||||||
|
}
|
||||||
|
repeater.Join(event.w)
|
||||||
|
case partEvent:
|
||||||
|
if ! ok {
|
||||||
|
log.Println("WARN: Parting an empty channel:", event.name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
repeater.Part(event.w)
|
||||||
|
if len(repeater.subscribers) == 0 {
|
||||||
|
delete(b.entries, event.name)
|
||||||
|
}
|
||||||
|
case sendEvent:
|
||||||
|
if ! ok {
|
||||||
|
log.Println("WARN: Sending to an empty channel:", event.name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
repeater.Send(event.p)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBook(t *testing.T) {
|
||||||
|
b := NewBook()
|
||||||
|
|
||||||
|
buf1 := bytes.NewBufferString("buf1")
|
||||||
|
b.Join("moo", buf1)
|
||||||
|
b.loop()
|
||||||
|
if len(b.entries) != 1 {
|
||||||
|
t.Error("Wrong number of entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send to an empty channel
|
||||||
|
b.Send("merf", []byte("goober"))
|
||||||
|
b.loop()
|
||||||
|
if buf1.String() != "buf1" {
|
||||||
|
t.Error("Sending to empty channel sent to non-empty channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send to a non-empty channel!
|
||||||
|
b.Send("moo", []byte("goober"))
|
||||||
|
b.loop()
|
||||||
|
if buf1.String() != "buf1goober" {
|
||||||
|
t.Error("Sending didn't work")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join another client
|
||||||
|
buf2 := bytes.NewBufferString("buf2")
|
||||||
|
b.Join("moo", buf2)
|
||||||
|
b.loop()
|
||||||
|
|
||||||
|
// Send to both
|
||||||
|
b.Send("moo", []byte("snerk"))
|
||||||
|
b.loop()
|
||||||
|
if buf1.String() != "buf1goobersnerk" {
|
||||||
|
t.Error("Send to 2-member channel busted", buf1)
|
||||||
|
}
|
||||||
|
if buf2.String() != "buf2snerk" {
|
||||||
|
t.Error("Send to 2-member channel busted", buf2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part a client
|
||||||
|
b.Part("moo", buf1)
|
||||||
|
b.loop()
|
||||||
|
|
||||||
|
b.Send("moo", []byte("peanut"))
|
||||||
|
b.loop()
|
||||||
|
if buf1.String() != "buf1goobersnerk" {
|
||||||
|
t.Error("Parted channel but still getting messages", buf1)
|
||||||
|
}
|
||||||
|
if buf2.String() != "buf2snerkpeanut" {
|
||||||
|
t.Error("Someone else parting somehow messed up sends", buf2)
|
||||||
|
}
|
||||||
|
}
|
30
main.go
30
main.go
|
@ -6,6 +6,22 @@ import (
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (ch Channel) Write(msg Message) {
|
||||||
|
for i, o := range clients {
|
||||||
|
if o.active == false {
|
||||||
|
nclients := len(clients)
|
||||||
|
clients[i] = clients[nclients - 1]
|
||||||
|
clients[nclients - 1] = Client{}
|
||||||
|
clients = clients[:nclients - 1]
|
||||||
|
} else if o == c {
|
||||||
|
// Don't send it back to the sending client
|
||||||
|
} else {
|
||||||
|
o.ws.Write(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ws *websocket.Conn
|
ws *websocket.Conn
|
||||||
active bool
|
active bool
|
||||||
|
@ -14,6 +30,7 @@ type Client struct {
|
||||||
var clients []Client
|
var clients []Client
|
||||||
|
|
||||||
func (c Client) Chat() {
|
func (c Client) Chat() {
|
||||||
|
websocket.Message.Receive
|
||||||
for c.active {
|
for c.active {
|
||||||
buf := make([]byte, 800)
|
buf := make([]byte, 800)
|
||||||
n, err := c.ws.Read(buf)
|
n, err := c.ws.Read(buf)
|
||||||
|
@ -21,19 +38,6 @@ func (c Client) Chat() {
|
||||||
c.active = false
|
c.active = false
|
||||||
}
|
}
|
||||||
buf = buf[:n]
|
buf = buf[:n]
|
||||||
|
|
||||||
for i, o := range clients {
|
|
||||||
if o.active == false {
|
|
||||||
nclients := len(clients)
|
|
||||||
clients[i] = clients[nclients - 1]
|
|
||||||
clients[nclients - 1] = Client{}
|
|
||||||
clients = clients[:nclients - 1]
|
|
||||||
} else if o == c {
|
|
||||||
// Don't send it back to the sending client
|
|
||||||
} else {
|
|
||||||
o.ws.Write(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
51
repeater.go
51
repeater.go
|
@ -5,62 +5,31 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repeater struct {
|
type Repeater struct {
|
||||||
joins chan io.Writer
|
|
||||||
parts chan io.Writer
|
|
||||||
sends chan []byte
|
|
||||||
subscribers []io.Writer
|
subscribers []io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepeater() *Repeater {
|
func NewRepeater() *Repeater {
|
||||||
return &Repeater{
|
return &Repeater{
|
||||||
joins: make(chan io.Writer, 5),
|
|
||||||
parts: make(chan io.Writer, 5),
|
|
||||||
sends: make(chan []byte, 5),
|
|
||||||
subscribers: make([]io.Writer, 0, 20),
|
subscribers: make([]io.Writer, 0, 20),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repeater) Join(w io.Writer) {
|
func (r *Repeater) Join(w io.Writer) {
|
||||||
r.joins <- w
|
r.subscribers = append(r.subscribers, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repeater) Part(w io.Writer) {
|
func (r *Repeater) Part(w io.Writer) {
|
||||||
r.parts <- w
|
for i, s := range r.subscribers {
|
||||||
|
if s == w {
|
||||||
|
nsubs := len(r.subscribers)
|
||||||
|
r.subscribers[i] = r.subscribers[nsubs-1]
|
||||||
|
r.subscribers = r.subscribers[:nsubs-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repeater) Send(p []byte) {
|
func (r *Repeater) Send(p []byte) {
|
||||||
r.sends <- p
|
for _, s := range r.subscribers {
|
||||||
}
|
s.Write(p)
|
||||||
|
|
||||||
func (r *Repeater) Close() {
|
|
||||||
close(r.sends)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repeater) Run() {
|
|
||||||
for r.loop() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repeater) loop() bool {
|
|
||||||
select {
|
|
||||||
case w := <- r.joins:
|
|
||||||
// Add subscriber
|
|
||||||
r.subscribers = append(r.subscribers, w)
|
|
||||||
case w := <- r.parts:
|
|
||||||
// Remove subscriber
|
|
||||||
for i, s := range r.subscribers {
|
|
||||||
if s == w {
|
|
||||||
nsubs := len(r.subscribers)
|
|
||||||
r.subscribers[i] = r.subscribers[nsubs-1]
|
|
||||||
r.subscribers = r.subscribers[:nsubs-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case p, ok := <- r.sends:
|
|
||||||
if ! ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, s := range r.subscribers {
|
|
||||||
s.Write(p)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,21 +10,17 @@ func TestRepeater(t *testing.T) {
|
||||||
|
|
||||||
buf1 := bytes.NewBufferString("buf1")
|
buf1 := bytes.NewBufferString("buf1")
|
||||||
r.Join(buf1)
|
r.Join(buf1)
|
||||||
r.loop()
|
|
||||||
if len(r.subscribers) != 1 {
|
if len(r.subscribers) != 1 {
|
||||||
t.Error("Joining did nothing")
|
t.Error("Joining did nothing")
|
||||||
}
|
}
|
||||||
r.Send([]byte("moo"))
|
r.Send([]byte("moo"))
|
||||||
r.loop()
|
|
||||||
if buf1.String() != "buf1moo" {
|
if buf1.String() != "buf1moo" {
|
||||||
t.Error("Client 1 not repeating", buf1)
|
t.Error("Client 1 not repeating", buf1)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf2 := bytes.NewBufferString("buf2")
|
buf2 := bytes.NewBufferString("buf2")
|
||||||
r.Join(buf2)
|
r.Join(buf2)
|
||||||
r.loop()
|
|
||||||
r.Send([]byte("bar"))
|
r.Send([]byte("bar"))
|
||||||
r.loop()
|
|
||||||
if buf1.String() != "buf1moobar" {
|
if buf1.String() != "buf1moobar" {
|
||||||
t.Error("Client 1 not repeating", buf1)
|
t.Error("Client 1 not repeating", buf1)
|
||||||
}
|
}
|
||||||
|
@ -33,21 +29,11 @@ func TestRepeater(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Part(buf1)
|
r.Part(buf1)
|
||||||
r.loop()
|
|
||||||
r.Send([]byte("baz"))
|
r.Send([]byte("baz"))
|
||||||
r.loop()
|
|
||||||
if buf1.String() != "buf1moobar" {
|
if buf1.String() != "buf1moobar" {
|
||||||
t.Error("Client 1 still getting data after part", buf1)
|
t.Error("Client 1 still getting data after part", buf1)
|
||||||
}
|
}
|
||||||
if buf2.String() != "buf2barbaz" {
|
if buf2.String() != "buf2barbaz" {
|
||||||
t.Error("Client 2 not getting data after part", buf2)
|
t.Error("Client 2 not getting data after part", buf2)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Close()
|
|
||||||
if r.loop() {
|
|
||||||
t.Error("Closed send didn't terminate loop")
|
|
||||||
}
|
|
||||||
if r.loop() {
|
|
||||||
t.Error("Second loop in terminated channel didn't terminate")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 72 KiB |
|
@ -7,7 +7,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
<img src="1280px-Morse-code-tree.svg.png">
|
<img src="code-tree.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
Loading…
Reference in New Issue