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