diff --git a/internal/connection/connection.go b/internal/connection/connection.go index 22fda07..adcafdb 100644 --- a/internal/connection/connection.go +++ b/internal/connection/connection.go @@ -61,29 +61,6 @@ func (c *conn) allocFd() int { return fd } -func (c *conn) onVppOpen(m msg.Msg) { - srvrReplyChan := make(chan im.Allocdocresp) - c.srvr <- im.Allocdoc{ - Reply: srvrReplyChan, - Name: m.Name, - } - - srvrResp := <-srvrReplyChan - if srvrResp.Err != nil { - panic("conn unable to Allocdoc") - } - - fd := c.allocFd() - doc := srvrResp.Doc - c.setDoc(fd, doc) - - doc <- im.Open{ - Conn: c.msgs, - Name: m.Name, - Fd: fd, - } -} - func (c *conn) getDoc(fd int) (chan interface{}, bool) { c.mu.Lock() defer c.mu.Unlock() @@ -108,6 +85,29 @@ func (c *conn) setDoc(fd int, doc chan interface{}) { c.fds[doc] = fd } +func (c *conn) onVppOpen(m msg.Msg) { + srvrReplyChan := make(chan im.Allocdocresp) + c.srvr <- im.Allocdoc{ + Reply: srvrReplyChan, + Name: m.Name, + } + + srvrResp := <-srvrReplyChan + if srvrResp.Err != nil { + panic("conn unable to Allocdoc") + } + + fd := c.allocFd() + doc := srvrResp.Doc + c.setDoc(fd, doc) + + doc <- im.Open{ + Conn: c.msgs, + Name: m.Name, + Fd: fd, + } +} + func (c *conn) onVppWrite(m msg.Msg) { doc, ok := c.getDoc(m.Fd) if !ok { diff --git a/internal/document/document.go b/internal/document/document.go index 494abe0..421513e 100644 --- a/internal/document/document.go +++ b/internal/document/document.go @@ -6,7 +6,7 @@ package document import ( im "github.com/mstone/focus/internal/msgs" "github.com/mstone/focus/ot" - // "gopkg.in/inconshreveable/log15.v2" + "gopkg.in/inconshreveable/log15.v2" ) // struct doc represents a vaporpad (like a file) @@ -72,38 +72,39 @@ func (d *doc) readLoop() { Rev: len(d.hist), } case im.Write: - // log15.Info("recv", "obj", "doc", "rev", v.Rev, "ops", v.Ops, "docrev", len(d.hist), "dochist", d.Body()) rev, ops := d.transform(v.Rev, v.Ops.Clone()) + log15.Info("recv", "obj", "doc", "rev", v.Rev, "ops", v.Ops, "docrev", len(d.hist), "dochist", d.Body(), "nrev", rev, "tops", ops) d.broadcast(v.Conn, rev, ops) } } } -func (d *doc) transform(rev int, ops ot.Ops) (int, ot.Ops) { +func (d *doc) transform(rev int, clientOps ot.Ops) (int, ot.Ops) { // extract concurrent ops - concurrentOps := []ot.Ops{} + concurrentServerOps := []ot.Ops{} if rev < len(d.hist) { - concurrentOps = d.hist[rev:] + concurrentServerOps = d.hist[rev:] } // compose concurrent ops - composedOps := ot.Ops{} - for _, concurrentOp := range concurrentOps { - composedOps = ot.Compose(composedOps, concurrentOp) + serverOps := ot.Ops{} + for _, concurrentOp := range concurrentServerOps { + serverOps = ot.Compose(serverOps, concurrentOp) } // produce transformed ops - transformedOps, _ := ot.Transform(ops, composedOps) + forServer, _ := ot.Transform(clientOps, serverOps) // update history - d.hist = append(d.hist, transformedOps) + // d.hist = append(d.hist, transformedOps) + d.hist = append(d.hist, forServer) // update composed ops for new conns - d.comp = ot.Compose(d.comp, transformedOps) + d.comp = ot.Compose(d.comp, forServer) rev = len(d.hist) - return rev, transformedOps + return rev, forServer } func (d *doc) broadcast(conn chan interface{}, rev int, ops ot.Ops) { diff --git a/internal/server/random_test.go b/internal/server/random_test.go index 849d5d1..d026107 100644 --- a/internal/server/random_test.go +++ b/internal/server/random_test.go @@ -4,11 +4,9 @@ package server import ( - "crypto/rand" "encoding/json" "fmt" log "gopkg.in/inconshreveable/log15.v2" - "math/big" "sync" "testing" "time" @@ -23,9 +21,11 @@ import ( // const numRounds = 3 // const numChars = 8 -const numClients = 10 -const numRounds = 1 -const numChars = 8 +const numClients = 3 +const numRounds = 3 +const numChars = 3 +const readTimeout = 50 * time.Millisecond +const writeTimeout = 500 * time.Millisecond type ws struct { rq, wq chan interface{} @@ -96,11 +96,8 @@ func (w *ws) CancelWriteTimeout() error { } type client struct { - mu sync.Mutex - wg *sync.WaitGroup clname string name string - fd int ws connection.WebSocket rev int doc *ot.Doc @@ -108,58 +105,26 @@ type client struct { numSend int numRecv int l log.Logger -} - -func randIntn(n int) int { - b, _ := rand.Int(rand.Reader, big.NewInt(int64(n))) - return int(b.Int64()) + hist []msg.Msg } func (c *client) sendRandomOps() { - c.mu.Lock() - defer c.mu.Unlock() - - ops := ot.Ops{} size := c.doc.Len() - op := 0 - if size > 0 { - op = randIntn(2) - } - switch op { - case 0: // insert - s := fmt.Sprintf("%x", randIntn(numChars)) - pos := 0 - if size > 0 { - pos = randIntn(size) - } - ops = ot.NewInsert(size, pos, s) - case 1: // delete - if size == 1 { - ops = ot.NewDelete(1, 0, 1) - } else { - d := randIntn(size) - pos := 0 - if size-d > 0 { - pos = randIntn(size - d) - } - ops = ot.NewDelete(size, pos, d) - } - } + ops := c.doc.GetRandomOps(numChars) - c.l.Info("genn", "ops", ops, "docsize", size, "doc", c.doc.String(), "docp", fmt.Sprintf("%p", c.doc)) - c.doc.Apply(ops.Clone()) + c.doc.Apply(ops) c.st = c.st.Client(c, ops.Clone()) + c.l.Info("genn", "ops", ops, "docsize", size, "doc", c.doc.String(), "docp", fmt.Sprintf("%p", c.doc), "clnhist", c.doc.String(), "clnst", c.st) } func (c *client) Send(ops ot.Ops) { - c.ws.SetWriteTimeout(1000 * time.Millisecond) + c.ws.SetWriteTimeout(writeTimeout) m := msg.Msg{ Cmd: msg.C_WRITE, - Fd: c.fd, Rev: c.rev, Ops: ops.Clone(), } - c.l.Info("send", "num", c.numSend, "rev", c.rev, "ops", ops) + // c.l.Info("send", "num", c.numSend, "rev", c.rev, "ops", ops) err := c.ws.WriteJSON(m) c.ws.CancelWriteTimeout() if err != nil { @@ -173,45 +138,37 @@ func (c *client) String() string { } func (c *client) Recv(rev int, ops ot.Ops) { - // c.l.Info("recv", "num", c.numRecv, "kind", "wrt", "rev", rev, "ops", ops, "clnrev", c.rev, "clnhist", c.doc.String()) c.doc.Apply(ops.Clone()) c.rev = rev + c.l.Info("stat", "body", c.doc.String(), "clnst", c.st) } func (c *client) Ack(rev int) { - // c.l.Info("recv", "num", c.numRecv, "kind", "ack", "rev", rev, "clnrev", c.rev, "clnhist", c.doc.String()) + c.l.Info("recv", "num", c.numRecv, "kind", "ack", "rev", rev, "clnrev", c.rev, "clnhist", c.doc.String(), "clnst", c.st) c.rev = rev } func (c *client) onWriteResp(m msg.Msg) { - c.mu.Lock() - defer c.mu.Unlock() - c.st = c.st.Ack(c, m.Rev) } func (c *client) onWrite(m msg.Msg) { - c.mu.Lock() - defer c.mu.Unlock() - + c.l.Info("recv", "num", c.numRecv, "kind", "wrt", "rev", m.Rev, "ops", m.Ops, "clnrev", c.rev, "clnhist", c.doc.String(), "clnst", c.st) c.st = c.st.Server(c, m.Rev, m.Ops.Clone()) } -func (c *client) writeLoop() { - defer c.wg.Done() - - for i := 0; i < numRounds; i++ { - c.sendRandomOps() - } -} - -func (c *client) readLoop() { - defer c.wg.Done() +func (c *client) loop() { + round := 0 Loop: for { + if round < numRounds { + c.sendRandomOps() + round++ + } + m := msg.Msg{} - c.ws.SetReadTimeout(500 * time.Millisecond) + c.ws.SetReadTimeout(readTimeout) err := c.ws.ReadJSON(&m) c.ws.CancelReadTimeout() if err != nil { @@ -240,90 +197,40 @@ func testOnce(t *testing.T) { clients := make([]*client, numClients) run := func(idx int) { - var err error defer wg.Done() // BUG(mistone): OPEN / really should probably fail, though we'll test that it works today. vpName := "/" - cwg := &sync.WaitGroup{} - conn, conn2 := NewWSPair() c := &client{ - mu: sync.Mutex{}, - wg: cwg, clname: fmt.Sprintf("%d", idx), name: vpName, rev: 0, doc: ot.NewDoc(), st: &ot.Synchronized{}, ws: conn, + hist: []msg.Msg{}, } c.l = log.New( "obj", "cln", "client", log.Lazy{c.String}, - "#", log.Lazy{func() int { - return c.numRecv + c.numSend - }}, ) clients[idx] = c focusSrv.Connect(conn2) - conn.SetWriteTimeout(1000 * time.Millisecond) - err = conn.WriteJSON(msg.Msg{ + conn.WriteJSON(msg.Msg{ Cmd: msg.C_OPEN, Name: vpName, }) - conn.CancelWriteTimeout() - if err != nil { - t.Errorf("unable to write OPEN, err: %q", err) - } // read open resp m := msg.Msg{} - conn.SetReadTimeout(1000 * time.Millisecond) - err = conn.ReadJSON(&m) - conn.CancelReadTimeout() - if err != nil { - t.Errorf("server unable to read OPEN_RESP, err: %q", err) - } - conn.CancelReadTimeout() - - if m.Cmd != msg.C_OPEN_RESP { - t.Errorf("client did not get an OPEN_RESP; msg: %+v", m) - } - - if m.Name != vpName { - t.Errorf("client got OPEN_RESP with wrong vaporpad: %s vs %+v", vpName, m) - } - c.name = vpName - c.fd = m.Fd - - // read open resp - m = msg.Msg{} - conn.SetReadTimeout(1000 * time.Millisecond) - err = conn.ReadJSON(&m) - conn.CancelReadTimeout() - if err != nil { - t.Errorf("server unable to read first WRITE, err: %q", err) - } - conn.CancelReadTimeout() + conn.ReadJSON(&m) - if m.Cmd != msg.C_WRITE { - t.Errorf("client did not get first WRITE; msg: %+v", m) - } - - if m.Fd != c.fd { - t.Errorf("client got first WRITE with wrong fd: %s vs %+v", c.fd, m) - } - c.onWrite(m) - - cwg.Add(2) - go c.writeLoop() - go c.readLoop() - cwg.Wait() + c.loop() } wg.Add(numClients) @@ -337,22 +244,33 @@ func testOnce(t *testing.T) { d <- im.Readall{sdrc} sdr := <-sdrc sd := sdr.Body + + log.Info("stat", "obj", "doc", "body", sd) + + for i := 0; i < numClients; i++ { + st := clients[i].st + log.Info("stat", "obj", "cln", "client", i, "body", clients[i].doc.String(), "clnst", st) + if !ot.IsSynchronized(st) { + t.Fatalf("unsynchronized client[%d]; state: %q", i, st) + } + } + for i := 0; i < numClients; i++ { s1 := clients[i].doc.String() if sd != s1 { - t.Fatalf("error, doc[%d] != server doc\n\t%q\n\t%q", i, s1, sd) + t.Fatalf("error, doc[%d] != server doc\n\t%q\n\t%q\n\tstate: %q", i, s1, sd, clients[i].st) } for j := i + 1; j < numClients; j++ { s2 := clients[j].doc.String() if s1 != s2 { - t.Fatalf("error, doc[%d] != doc[%d]\n\t%q\n\t%q", i, j, s1, s2) + t.Fatalf("error, doc[%d] != doc[%d]\n\t%q\n\t%q\n\tstate1: %q\n\tstate2: %q", i, j, s1, s2, clients[i].st, clients[j].st) } } } } func TestRandom(t *testing.T) { - for i := 0; i < 90; i++ { + for i := 0; i < 300; i++ { testOnce(t) } } diff --git a/ot/client.go b/ot/client.go index 0e808af..5e20ecb 100644 --- a/ot/client.go +++ b/ot/client.go @@ -44,12 +44,19 @@ type Client interface { Ack(rev int) } -type Synchronized struct{} +func IsSynchronized(st State) bool { + switch st.(type) { + case *Synchronized: + return true + default: + return false + } +} -var synchronized = &Synchronized{} +type Synchronized struct{} func (s *Synchronized) String() string { - return "Synchronized{}" + return "Synchronized" } func (s *Synchronized) Client(c Client, ops Ops) State { @@ -75,7 +82,7 @@ type Waiting struct { } func (w *Waiting) String() string { - return fmt.Sprintf("Waiting{inflight: %s}", w.inflight) + return fmt.Sprintf("Waiting %s", w.inflight) } func (w *Waiting) Client(c Client, ops Ops) State { @@ -95,7 +102,7 @@ func (w *Waiting) Server(c Client, rev int, ops Ops) State { func (w *Waiting) Ack(c Client, rev int) State { c.Ack(rev) - return synchronized + return &Synchronized{} } type Buffering struct { @@ -104,7 +111,7 @@ type Buffering struct { } func (b *Buffering) String() string { - return fmt.Sprintf("Buffering{inflight: %s, waiting: %s}", b.inflight, b.waiting) + return fmt.Sprintf("Buffering %s %s", b.inflight, b.waiting) } func (b *Buffering) Client(c Client, ops Ops) State { diff --git a/ot/ot.go b/ot/ot.go index 77f42a4..1b83f50 100644 --- a/ot/ot.go +++ b/ot/ot.go @@ -31,8 +31,10 @@ package ot import ( "bytes" + "crypto/rand" "encoding/json" "fmt" + "math/big" "strings" "sync" "unicode/utf8" @@ -615,3 +617,39 @@ func (d *Doc) Apply(os Ops) { idx += len(p) } } + +func RandIntn(n int) int { + b, _ := rand.Int(rand.Reader, big.NewInt(int64(n))) + return int(b.Int64()) +} + +func (d *Doc) GetRandomOps(numChars int) Ops { + ops := Ops{} + size := d.Len() + op := 0 + if size > 0 { + op = RandIntn(2) + } + switch op { + case 0: // insert + s := fmt.Sprintf("%x", RandIntn(numChars*8)) + pos := 0 + if size > 0 { + pos = RandIntn(size) + } + ops = NewInsert(size, pos, s) + case 1: // delete + if size == 1 { + ops = NewDelete(1, 0, 1) + } else { + d := RandIntn(size) + pos := 0 + if size-d > 0 { + pos = RandIntn(size - d) + } + ops = NewDelete(size, pos, d) + } + } + + return ops.Clone() +} diff --git a/ot/ot_test.go b/ot/ot_test.go index 1b3f2e9..2ea43f9 100644 --- a/ot/ot_test.go +++ b/ot/ot_test.go @@ -4,10 +4,7 @@ package ot import ( - "crypto/rand" "encoding/json" - "fmt" - "math/big" "reflect" "testing" ) @@ -398,49 +395,13 @@ func TestTransform(t *testing.T) { doTransformTable(t, table) } -func randIntn(n int) int { - b, _ := rand.Int(rand.Reader, big.NewInt(int64(n))) - return int(b.Int64()) -} - -func getRandomOps(d *Doc, numChars int) Ops { - ops := Ops{} - size := d.Len() - op := 0 - if size > 0 { - op = randIntn(2) - } - switch op { - case 0: // insert - s := fmt.Sprintf("%x", randIntn(numChars*8)) - pos := 0 - if size > 0 { - pos = randIntn(size) - } - ops = NewInsert(size, pos, s) - case 1: // delete - if size == 1 { - ops = NewDelete(1, 0, 1) - } else { - d := randIntn(size) - pos := 0 - if size-d > 0 { - pos = randIntn(size - d) - } - ops = NewDelete(size, pos, d) - } - } - - return ops.Clone() -} - func testOneCompose(t *testing.T) { composedOps := Ops{} d1 := NewDoc() for i := 0; i < 100; i++ { - ops := getRandomOps(d1, 4) + ops := d1.GetRandomOps(4) d1.Apply(ops) composedOps = Compose(composedOps, ops) } @@ -469,10 +430,10 @@ func testOneTransform(t *testing.T) { a2 := Ops{} for i := 0; i < 100; i++ { - o1 := getRandomOps(d1, 4) + o1 := d1.GetRandomOps(4) d1.Apply(o1) - o2 := getRandomOps(d2, 4) + o2 := d2.GetRandomOps(4) d2.Apply(o2) a1 = Compose(a1, o1) diff --git a/sequence/sequence.go b/sequence/sequence.go index 1f9c4a8..d28d98c 100644 --- a/sequence/sequence.go +++ b/sequence/sequence.go @@ -17,31 +17,37 @@ import ( var in = flag.String("i", "focus.err", "input file") var out = flag.String("o", "-", "output file") var numClients = flag.Int("c", 4, "number of clients") -var numDocs = flag.Int("d", 1, "number of clients") +var numDocs = flag.Int("d", 1, "number of documents") type Record struct { - Msg string - Obj string - Action string - Cmd string - Conn int - Doc string - Client int - Name string - Fd int - Rev int - Ops string - Comp string - Hist string - Body string - From string - To string - Pdoc string - Ndoc string - Prev string - Nrev string - Pstate string - Nstate string + Msg string + Obj string + Action string + Cmd string + Conn int + Doc string + Client int + Name string + Fd int + Rev int + Ops string + Comp string + Hist string + Body string + From string + To string + Pdoc string + Ndoc string + Prev string + Nrev string + Pstate string + Nstate string + Kind string + Clnst string + Clnhist string + Docrev int + Dochist string + Tops string } func main() { @@ -159,13 +165,11 @@ func main() { outf.WriteString(fmt.Sprintf(` \newthreadx{1}{d%d}{Doc %d}`+"\n", i, i)) } for i := 0; i < *numClients; i++ { - outf.WriteString(fmt.Sprintf(` \newthreadx{2}{c%d}{Client %d}`+"\n", i, i)) + outf.WriteString(fmt.Sprintf(` \newthreadx{3}{c%d}{Client %d}`+"\n", i, i)) } scanner := bufio.NewScanner(inf) - var rec, prev Record - - fdmap := map[int]int{} + var rec Record for scanner.Scan() { line := scanner.Text() @@ -175,92 +179,57 @@ func main() { log.Error("unmarshal err", "line", line, "err", err) } - if rec.Obj == "client" { - log.Info("found record", "rec", rec) - switch rec.Action { - case "SEND": + if rec.Obj == "cln" { + switch rec.Msg { + case "genn": + at := fmt.Sprintf("c%d", rec.Client) + before := fmt.Sprintf("gen %s : %s : %s", rec.Ops, rec.Clnst, rec.Clnhist) + after := fmt.Sprintf("") + fmt.Fprintf(outf, `\begin{callself}{%s}{\footnotesize %s}{\footnotesize %s} + \end{callself}`+"\n", at, before, after) + case "stat": at := fmt.Sprintf("c%d", rec.Client) - before := fmt.Sprintf("send [%s], %s", rec.Pdoc, rec.Ops) - after := fmt.Sprintf("[%s]", rec.Ndoc) - if rec.Msg == "client send returned" { - outf.WriteString(fmt.Sprintf(`\begin{callself}{%s}{\footnotesize %s}{\footnotesize %s} - \end{callself}`+"\n", at, before, after)) - // \node [right=2mm of sc\thecallevel] {\footnotesize %s}; + before := fmt.Sprintf("stat %s : %s", rec.Body, rec.Clnst) + after := fmt.Sprintf("") + fmt.Fprintf(outf, `\begin{callself}{%s}{\footnotesize %s}{\footnotesize %s} + \end{callself}`+"\n", at, before, after) + case "recv": + from := "d0" + var label string + switch rec.Kind { + case "wrt": + label = fmt.Sprintf("wrt %d %s : %s : %s", rec.Rev, rec.Ops, rec.Clnst, rec.Clnhist) + case "ack": + label = fmt.Sprintf("ack %d", rec.Rev) } - case "STAT": - at := fmt.Sprintf("c%d", fdmap[rec.Fd]) - before := fmt.Sprintf("recv %s [%s]", rec.Ops, rec.Pdoc) - after := fmt.Sprintf("[%s]", rec.Ndoc) - if rec.Msg == "client recv done" && prev.Action == "SEND" { - // (#2)+(0,-\theseqlevel*\unitfactor-0.7*\unitfactor) node (mess from) {}; - // outf.WriteString(fmt.Sprintf("\\node [below right=0mm of mess to] {[%s]};", contents)) - outf.WriteString(fmt.Sprintf("\\begin{callself}{%s}{\\footnotesize %s}{\\footnotesize %s}\\end{callself}\n", at, before, after)) - // outf.WriteString(fmt.Sprintf(" (%s)+(0,-\\theseqlevel*\\unitfactor-0.7*\\unitfactor) \\node (stat) {%s};\n", at, contents)) + to := fmt.Sprintf("c%d", rec.Client) + dir := "R" + start := "" + end := "" + labelPos := "" + if rec.Conn%2 == 1 { + labelPos = "[midway, above right = -2mm and 10mm, font=\\footnotesize, blue]" + } else { + labelPos = "[midway, above, font=\\footnotesize, blue]" } + fmt.Fprintf(outf, " \\bloodymess[1]{%s}{%s}{%s}{%s}{%s}{%s}{%s}\n", + from, label, to, dir, start, end, labelPos) } } if rec.Obj == "doc" { - if rec.Action == "SEND" || rec.Action == "RECV" || rec.Action == "STAT" { - // log.Info("found record", "rec", rec) - - switch rec.Action { - case "STAT": - contents := rec.Body - if prev.Action == "RECV" { - outf.WriteString(fmt.Sprintf(" \\node [below left=0mm of mess to] {\\footnotesize [%s]};\n", contents)) - } - case "SEND": - from := "d0" - var label string - switch rec.Cmd { - case "openresp": - label = fmt.Sprintf("%s %s %d", rec.Cmd, rec.Name, rec.Fd) - fdmap[rec.Fd] = rec.Conn - case "write": - label = fmt.Sprintf("%s %d %s", rec.Cmd, rec.Rev, rec.Ops) - case "writeresp": - label = fmt.Sprintf("%s %d", rec.Cmd, rec.Rev) - } - to := fmt.Sprintf("c%d", rec.Conn) - dir := "R" - start := "" - end := "" - // labelPos := "[midway, above, font=\\footnotesize]" // right = 2mm and 2mm" - labelPos := "" - if rec.Conn%2 == 1 { - labelPos = "[midway, above right = -2mm and 10mm, font=\\footnotesize, blue]" - } else { - labelPos = "[midway, above, font=\\footnotesize, blue]" - } - fmt.Fprintf(outf, " \\bloodymess[1]{%s}{%s}{%s}{%s}{%s}{%s}{%s}\n", - from, label, to, dir, start, end, labelPos) - case "RECV": - from := fmt.Sprintf("c%d", rec.Conn) - label := "" - switch rec.Cmd { - default: - label = fmt.Sprintf("%s %d, %s", rec.Cmd, rec.Rev, rec.Ops) - case "open": - label = fmt.Sprintf("%s %s", rec.Cmd, rec.Name) - case "write": - label = fmt.Sprintf("%s %d %s", rec.Cmd, rec.Rev, rec.Ops) - } - to := "d0" - dir := "L" - start := "" - end := "" - // labelPos := "[midway, above, font=\\footnotesize]" // right = 2mm and 2mm" - labelPos := "" - if rec.Conn%2 == 1 { - labelPos = "[midway, above left = -2mm and 10mm, font=\\footnotesize, red]" - } else { - labelPos = "[midway, above, font=\\footnotesize, red]" - } - fmt.Fprintf(outf, " \\bloodymess[1]{%s}{%s}{%s}{%s}{%s}{%s}{%s}\n", - from, label, to, dir, start, end, labelPos) - } - - prev = rec + switch rec.Msg { + case "recv": + at := "d0" + before := fmt.Sprintf("recv %d %s $\\rightarrow$ %s", rec.Rev, rec.Ops, rec.Tops) + after := fmt.Sprintf("%d : %s", rec.Docrev, rec.Dochist) + fmt.Fprintf(outf, `\begin{callself}{%s}{\footnotesize %s}{\footnotesize %s} + \end{callself}`+"\n", at, before, after) + case "stat": + at := fmt.Sprintf("d0") + before := fmt.Sprintf("stat %s", rec.Body) + after := fmt.Sprintf("") + fmt.Fprintf(outf, `\begin{callself}{%s}{\footnotesize %s}{\footnotesize %s} + \end{callself}`+"\n", at, before, after) } } } @@ -272,3 +241,20 @@ func main() { \end{document}`) } + +// from := fmt.Sprintf("c%d", rec.Conn) +// from := fmt.Sprintf("c%d", rec.Conn) +// label := fmt.Sprintf("wrt %d %s", rec.Rev, rec.Ops) +// to := "d0" +// dir := "L" +// start := "" +// end := "" +// // labelPos := "[midway, above, font=\\footnotesize]" // right = 2mm and 2mm" +// labelPos := "" +// if rec.Conn%2 == 1 { +// labelPos = "[midway, above left = -2mm and 10mm, font=\\footnotesize, red]" +// } else { +// labelPos = "[midway, above, font=\\footnotesize, red]" +// } +// fmt.Fprintf(outf, " \\bloodymess[1]{%s}{%s}{%s}{%s}{%s}{%s}{%s}\n", +// from, label, to, dir, start, end, labelPos)