parent
							
								
									eafc496037
								
							
						
					
					
						commit
						b66e36f0e3
					
				| @ -0,0 +1,134 @@ | ||||
| package acedoc | ||||
| 
 | ||||
| import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func (d *Document) Client(h DeltaHandler) *Client { | ||||
| 	c := &Client{ | ||||
| 		ch:      make(chan Delta, 10), | ||||
| 		errs:    make(chan error, 10), | ||||
| 		doc:     d, | ||||
| 		handler: h, | ||||
| 	} | ||||
| 
 | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			dls := c.pullDeltas() | ||||
| 			if dls != nil && c.handler != nil { | ||||
| 				go func() { | ||||
| 					err := c.handler.Handle(dls) | ||||
| 					if err != nil { | ||||
| 						select { | ||||
| 						case c.errs <- err: | ||||
| 						default: | ||||
| 							c.Close() | ||||
| 							return | ||||
| 						} | ||||
| 					} | ||||
| 				}() | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	d.mtx.Lock() | ||||
| 	defer d.mtx.Unlock() | ||||
| 
 | ||||
| 	d.clients = append(d.clients, c) | ||||
| 
 | ||||
| 	return c | ||||
| } | ||||
| 
 | ||||
| func (d *Document) removeClient(c *Client) { | ||||
| 	d.mtx.Lock() | ||||
| 	defer d.mtx.Unlock() | ||||
| 
 | ||||
| 	for i, ci := range d.clients { | ||||
| 		if ci == c { | ||||
| 			newclients := append([]*Client{}, d.clients[:i]...) | ||||
| 			if len(d.clients) > i { | ||||
| 				newclients = append(newclients, d.clients[i+1:]...) | ||||
| 			} | ||||
| 			d.clients = newclients | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type Client struct { | ||||
| 	ch   chan Delta | ||||
| 	errs chan error | ||||
| 
 | ||||
| 	closing sync.Mutex | ||||
| 	closed  bool | ||||
| 
 | ||||
| 	doc            *Document | ||||
| 	maxDeltaBucket int | ||||
| 	pending        []Delta | ||||
| 	handler        DeltaHandler | ||||
| } | ||||
| 
 | ||||
| // Close closes the client
 | ||||
| func (c *Client) Close() { | ||||
| 	c.closing.Lock() | ||||
| 	defer c.closing.Unlock() | ||||
| 
 | ||||
| 	if !c.closed { | ||||
| 		c.doc.removeClient(c) | ||||
| 		close(c.ch) | ||||
| 		close(c.errs) | ||||
| 		c.closed = true | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Error returns any pending errors from pushing deltas to the client.
 | ||||
| func (c *Client) Error() error { | ||||
| 	var err error | ||||
| 	select { | ||||
| 	case err = <-c.errs: | ||||
| 		return err | ||||
| 	default: | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // PushDeltas pushes deltas into the document.
 | ||||
| func (c *Client) PushDeltas(d ...Delta) error { | ||||
| 	return c.doc.Apply(d...) | ||||
| } | ||||
| 
 | ||||
| func (c *Client) pullDeltas() []Delta { | ||||
| 	maxSize := c.maxDeltaBucket | ||||
| 	if maxSize == 0 { | ||||
| 		maxSize = 50 | ||||
| 	} | ||||
| 
 | ||||
| 	timeout := 10 * time.Microsecond | ||||
| 
 | ||||
| 	var ds []Delta | ||||
| 	for { | ||||
| 		var d Delta | ||||
| 		var ok bool | ||||
| 
 | ||||
| 		select { | ||||
| 		case d, ok = <-c.ch: | ||||
| 		case <-time.After(timeout): | ||||
| 			return ds | ||||
| 		} | ||||
| 
 | ||||
| 		if !ok { | ||||
| 			return ds | ||||
| 		} | ||||
| 
 | ||||
| 		if d.Equal(Delta{}) { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		ds = append(ds, d) | ||||
| 		if len(ds) == maxSize { | ||||
| 			return ds | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| package acedoc | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func TestClient(t *testing.T) { | ||||
| 	doc := NewString("") | ||||
| 
 | ||||
| 	aliceCh := make(chan []Delta, 5) | ||||
| 	alice := doc.Client(DeltaHandlerFunc(func(ds []Delta) error { | ||||
| 		aliceCh <- ds | ||||
| 		return nil | ||||
| 	})) | ||||
| 	defer alice.Close() | ||||
| 
 | ||||
| 	bobCh := make(chan []Delta, 5) | ||||
| 	bob := doc.Client(DeltaHandlerFunc(func(ds []Delta) error { | ||||
| 		bobCh <- ds | ||||
| 		return nil | ||||
| 	})) | ||||
| 	defer bob.Close() | ||||
| 
 | ||||
| 	delta := Insert(0, 0, "alphabet") | ||||
| 
 | ||||
| 	err := alice.PushDeltas(delta) | ||||
| 	if err != nil { | ||||
| 		t.Error("got error pushing delta:", err) | ||||
| 	} | ||||
| 
 | ||||
| 	select { | ||||
| 	case got := <-bobCh: | ||||
| 		if len(got) != 1 { | ||||
| 			t.Errorf("got %d deltas, expected %d", len(got), 1) | ||||
| 		} else if !got[0].Equal(delta) { | ||||
| 			t.Error("didn't get the correct delta, saw:", got[0], "expected:", delta) | ||||
| 		} | ||||
| 	case <-time.After(10 * time.Millisecond): | ||||
| 		t.Error("didn't get any delta") | ||||
| 	} | ||||
| 
 | ||||
| 	if got := doc.Contents(); got != "alphabet" { | ||||
| 		t.Errorf("saw doc %q, expected %q", got, "alphabet") | ||||
| 	} | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue