Skip to content

Commit 07714de

Browse files
committed
WindowWithTimeOrCount operator
1 parent 630a692 commit 07714de

6 files changed

+229
-171
lines changed

duration.go

+24-26
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,23 @@ type duration struct {
1919
d time.Duration
2020
}
2121

22-
type testDuration struct {
23-
fs []func()
22+
func (d *duration) duration() time.Duration {
23+
return d.d
24+
}
25+
26+
// WithDuration is a duration option
27+
func WithDuration(d time.Duration) Duration {
28+
return &duration{
29+
d: d,
30+
}
2431
}
2532

2633
var tick = struct{}{}
2734

35+
type causalityDuration struct {
36+
fs []func()
37+
}
38+
2839
func timeCausality(elems ...interface{}) (context.Context, Observable, Duration) {
2940
ch := make(chan Item, 1)
3041
fs := make([]func(), len(elems)+1)
@@ -35,27 +46,25 @@ func timeCausality(elems ...interface{}) (context.Context, Observable, Duration)
3546
if elem == tick {
3647
fs[i] = func() {}
3748
} else {
38-
fs[i] = func() {
39-
ch <- Of(elem)
49+
switch elem := elem.(type) {
50+
default:
51+
fs[i] = func() {
52+
ch <- Of(elem)
53+
}
54+
case error:
55+
fs[i] = func() {
56+
ch <- Error(elem)
57+
}
4058
}
4159
}
4260
}
4361
fs[len(elems)] = func() {
4462
cancel()
4563
}
46-
return ctx, FromChannel(ch), &testDuration{fs: fs}
47-
}
48-
49-
func (d *testDuration) append(fs ...func()) {
50-
if d.fs == nil {
51-
d.fs = make([]func(), 0)
52-
}
53-
for _, f := range fs {
54-
d.fs = append(d.fs, f)
55-
}
64+
return ctx, FromChannel(ch), &causalityDuration{fs: fs}
5665
}
5766

58-
func (d *testDuration) duration() time.Duration {
67+
func (d *causalityDuration) duration() time.Duration {
5968
d.fs[0]()
6069
d.fs = d.fs[1:]
6170
return 0
@@ -69,14 +78,3 @@ func (m *mockDuration) duration() time.Duration {
6978
args := m.Called()
7079
return args.Get(0).(time.Duration)
7180
}
72-
73-
func (d *duration) duration() time.Duration {
74-
return d.d
75-
}
76-
77-
// WithDuration is a duration option
78-
func WithDuration(d time.Duration) Duration {
79-
return &duration{
80-
d: d,
81-
}
82-
}

item.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ func (i Item) SendBlocking(ch chan<- Item) {
7474
ch <- i
7575
}
7676

77-
// SendContext sends an item and blocks until it is sent or a context canceled.
77+
// SendWithContext sends an item and blocks until it is sent or a context canceled.
7878
// It returns a boolean to indicate whether the item was sent.
79-
func (i Item) SendContext(ctx context.Context, ch chan<- Item) bool {
79+
func (i Item) SendWithContext(ctx context.Context, ch chan<- Item) bool {
8080
select {
8181
case <-ctx.Done():
8282
return false

item_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func Test_Item_SendContext_True(t *testing.T) {
4343
defer close(ch)
4444
ctx, cancel := context.WithCancel(context.Background())
4545
defer cancel()
46-
assert.True(t, Of(5).SendContext(ctx, ch))
46+
assert.True(t, Of(5).SendWithContext(ctx, ch))
4747
}
4848

4949
func Test_Item_SendNonBlocking(t *testing.T) {

observable.go

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type Observable interface {
7575
Unmarshal(unmarshaller Unmarshaller, factory func() interface{}, opts ...Option) Observable
7676
WindowWithCount(count int, opts ...Option) Observable
7777
WindowWithTime(timespan Duration, opts ...Option) Observable
78+
WindowWithTimeOrCount(timespan Duration, count int, opts ...Option) Observable
7879
ZipFromIterable(iterable Iterable, zipper Func2, opts ...Option) Observable
7980
}
8081

observable_operator.go

+141-50
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,8 @@ func (o *ObservableImpl) BufferWithTime(timespan Duration, opts ...Option) Obser
481481
if item.Error() {
482482
next <- item
483483
return
484-
} else {
485-
buffer = append(buffer, item.V)
486484
}
485+
buffer = append(buffer, item.V)
487486
case <-time.After(timespan.duration()):
488487
if len(buffer) != 0 {
489488
select {
@@ -542,16 +541,15 @@ func (o *ObservableImpl) BufferWithTimeOrCount(timespan Duration, count int, opt
542541
if item.Error() {
543542
next <- item
544543
return
545-
} else {
546-
buffer = append(buffer, item.V)
547-
if len(buffer) == count {
548-
select {
549-
case <-ctx.Done():
550-
return
551-
case next <- Of(buffer):
552-
}
553-
buffer = make([]interface{}, 0)
544+
}
545+
buffer = append(buffer, item.V)
546+
if len(buffer) == count {
547+
select {
548+
case <-ctx.Done():
549+
return
550+
case next <- Of(buffer):
554551
}
552+
buffer = make([]interface{}, 0)
555553
}
556554
case <-time.After(timespan.duration()):
557555
if len(buffer) != 0 {
@@ -2501,65 +2499,158 @@ func (op *windowWithCountOperator) end(_ context.Context, _ chan<- Item) {
25012499
func (op *windowWithCountOperator) gatherNext(_ context.Context, _ Item, _ chan<- Item, _ operatorOptions) {
25022500
}
25032501

2502+
// WindowWithTime periodically subdivides items from an Observable into Observables based on timed windows
2503+
// and emit them rather than emitting the items one at a time.
25042504
func (o *ObservableImpl) WindowWithTime(timespan Duration, opts ...Option) Observable {
25052505
if timespan == nil {
25062506
return Thrown(IllegalInputError{error: "timespan must no be nil"})
25072507
}
25082508

2509-
//option := parseOptions(opts...)
2509+
f := func(ctx context.Context, next chan Item, option Option, opts ...Option) {
2510+
defer close(next)
2511+
observe := o.Observe(opts...)
2512+
ch := option.buildChannel()
2513+
empty := true
2514+
if !Of(FromChannel(ch)).SendWithContext(ctx, next) {
2515+
return
2516+
}
2517+
2518+
for {
2519+
select {
2520+
case <-ctx.Done():
2521+
close(ch)
2522+
return
2523+
case item, ok := <-observe:
2524+
if !ok {
2525+
close(ch)
2526+
return
2527+
}
2528+
if item.Error() {
2529+
if !item.SendWithContext(ctx, ch) {
2530+
return
2531+
}
2532+
if option.getErrorStrategy() == Stop {
2533+
close(ch)
2534+
return
2535+
}
2536+
}
2537+
if !item.SendWithContext(ctx, ch) {
2538+
return
2539+
}
2540+
empty = false
2541+
case <-time.After(timespan.duration()):
2542+
if empty {
2543+
continue
2544+
}
2545+
close(ch)
2546+
ch = option.buildChannel()
2547+
empty = true
2548+
if !Of(FromChannel(ch)).SendWithContext(ctx, next) {
2549+
return
2550+
}
2551+
}
2552+
}
2553+
}
2554+
2555+
option := parseOptions(opts...)
2556+
2557+
if option.isEagerObservation() {
2558+
next := option.buildChannel()
2559+
ctx := option.buildContext()
2560+
go f(ctx, next, option, opts...)
2561+
return &ObservableImpl{iterable: newChannelIterable(next)}
2562+
}
25102563

2511-
// TODO Handle eager observation
25122564
return &ObservableImpl{
25132565
iterable: newFactoryIterable(func(propagatedOptions ...Option) <-chan Item {
25142566
mergedOptions := append(opts, propagatedOptions...)
25152567
option := parseOptions(mergedOptions...)
25162568
next := option.buildChannel()
25172569
ctx := option.buildContext()
2570+
go f(ctx, next, option, mergedOptions...)
2571+
return next
2572+
}),
2573+
}
2574+
}
25182575

2519-
go func() {
2520-
defer close(next)
2521-
observe := o.Observe(mergedOptions...)
2522-
ch := option.buildChannel()
2523-
empty := true
2524-
select {
2525-
case <-ctx.Done():
2576+
func (o *ObservableImpl) WindowWithTimeOrCount(timespan Duration, count int, opts ...Option) Observable {
2577+
if timespan == nil {
2578+
return Thrown(IllegalInputError{error: "timespan must no be nil"})
2579+
}
2580+
if count < 0 {
2581+
return Thrown(IllegalInputError{error: "count must be positive or nil"})
2582+
}
2583+
2584+
f := func(ctx context.Context, next chan Item, option Option, opts ...Option) {
2585+
defer close(next)
2586+
observe := o.Observe(opts...)
2587+
ch := option.buildChannel()
2588+
iCount := 0
2589+
if !Of(FromChannel(ch)).SendWithContext(ctx, next) {
2590+
return
2591+
}
2592+
2593+
for {
2594+
select {
2595+
case <-ctx.Done():
2596+
close(ch)
2597+
return
2598+
case item, ok := <-observe:
2599+
if !ok {
2600+
close(ch)
25262601
return
2527-
case next <- Of(FromChannel(ch)):
25282602
}
2529-
2530-
for {
2531-
select {
2532-
case <-ctx.Done():
2603+
if item.Error() {
2604+
if !item.SendWithContext(ctx, ch) {
25332605
return
2534-
case item, ok := <-observe:
2535-
if !ok {
2536-
close(ch)
2537-
return
2538-
}
2539-
if item.Error() {
2540-
ch <- item
2541-
close(ch)
2542-
return
2543-
} else {
2544-
ch <- item
2545-
empty = false
2546-
}
2547-
case <-time.After(timespan.duration()):
2548-
if empty {
2549-
continue
2550-
}
2606+
}
2607+
if option.getErrorStrategy() == Stop {
25512608
close(ch)
2552-
ch = option.buildChannel()
2553-
empty = true
2554-
select {
2555-
case <-ctx.Done():
2556-
return
2557-
case next <- Of(FromChannel(ch)):
2558-
}
2609+
return
25592610
}
25602611
}
2561-
}()
2612+
if !item.SendWithContext(ctx, ch) {
2613+
return
2614+
}
2615+
iCount++
2616+
if iCount == count {
2617+
close(ch)
2618+
ch = option.buildChannel()
2619+
iCount = 0
2620+
if !Of(FromChannel(ch)).SendWithContext(ctx, next) {
2621+
return
2622+
}
2623+
}
2624+
case <-time.After(timespan.duration()):
2625+
if iCount == 0 {
2626+
continue
2627+
}
2628+
close(ch)
2629+
ch = option.buildChannel()
2630+
iCount = 0
2631+
if !Of(FromChannel(ch)).SendWithContext(ctx, next) {
2632+
return
2633+
}
2634+
}
2635+
}
2636+
}
2637+
2638+
option := parseOptions(opts...)
2639+
2640+
if option.isEagerObservation() {
2641+
next := option.buildChannel()
2642+
ctx := option.buildContext()
2643+
go f(ctx, next, option, opts...)
2644+
return &ObservableImpl{iterable: newChannelIterable(next)}
2645+
}
25622646

2647+
return &ObservableImpl{
2648+
iterable: newFactoryIterable(func(propagatedOptions ...Option) <-chan Item {
2649+
mergedOptions := append(opts, propagatedOptions...)
2650+
option := parseOptions(mergedOptions...)
2651+
next := option.buildChannel()
2652+
ctx := option.buildContext()
2653+
go f(ctx, next, option, mergedOptions...)
25632654
return next
25642655
}),
25652656
}

0 commit comments

Comments
 (0)