github.com/lingyao2333/mo-zero@v1.4.1/core/fx/stream.go (about) 1 package fx 2 3 import ( 4 "sort" 5 "sync" 6 7 "github.com/lingyao2333/mo-zero/core/collection" 8 "github.com/lingyao2333/mo-zero/core/lang" 9 "github.com/lingyao2333/mo-zero/core/threading" 10 ) 11 12 const ( 13 defaultWorkers = 16 14 minWorkers = 1 15 ) 16 17 type ( 18 rxOptions struct { 19 unlimitedWorkers bool 20 workers int 21 } 22 23 // FilterFunc defines the method to filter a Stream. 24 FilterFunc func(item interface{}) bool 25 // ForAllFunc defines the method to handle all elements in a Stream. 26 ForAllFunc func(pipe <-chan interface{}) 27 // ForEachFunc defines the method to handle each element in a Stream. 28 ForEachFunc func(item interface{}) 29 // GenerateFunc defines the method to send elements into a Stream. 30 GenerateFunc func(source chan<- interface{}) 31 // KeyFunc defines the method to generate keys for the elements in a Stream. 32 KeyFunc func(item interface{}) interface{} 33 // LessFunc defines the method to compare the elements in a Stream. 34 LessFunc func(a, b interface{}) bool 35 // MapFunc defines the method to map each element to another object in a Stream. 36 MapFunc func(item interface{}) interface{} 37 // Option defines the method to customize a Stream. 38 Option func(opts *rxOptions) 39 // ParallelFunc defines the method to handle elements parallelly. 40 ParallelFunc func(item interface{}) 41 // ReduceFunc defines the method to reduce all the elements in a Stream. 42 ReduceFunc func(pipe <-chan interface{}) (interface{}, error) 43 // WalkFunc defines the method to walk through all the elements in a Stream. 44 WalkFunc func(item interface{}, pipe chan<- interface{}) 45 46 // A Stream is a stream that can be used to do stream processing. 47 Stream struct { 48 source <-chan interface{} 49 } 50 ) 51 52 // Concat returns a concatenated Stream. 53 func Concat(s Stream, others ...Stream) Stream { 54 return s.Concat(others...) 55 } 56 57 // From constructs a Stream from the given GenerateFunc. 58 func From(generate GenerateFunc) Stream { 59 source := make(chan interface{}) 60 61 threading.GoSafe(func() { 62 defer close(source) 63 generate(source) 64 }) 65 66 return Range(source) 67 } 68 69 // Just converts the given arbitrary items to a Stream. 70 func Just(items ...interface{}) Stream { 71 source := make(chan interface{}, len(items)) 72 for _, item := range items { 73 source <- item 74 } 75 close(source) 76 77 return Range(source) 78 } 79 80 // Range converts the given channel to a Stream. 81 func Range(source <-chan interface{}) Stream { 82 return Stream{ 83 source: source, 84 } 85 } 86 87 // AllMach returns whether all elements of this stream match the provided predicate. 88 // May not evaluate the predicate on all elements if not necessary for determining the result. 89 // If the stream is empty then true is returned and the predicate is not evaluated. 90 func (s Stream) AllMach(predicate func(item interface{}) bool) bool { 91 for item := range s.source { 92 if !predicate(item) { 93 // make sure the former goroutine not block, and current func returns fast. 94 go drain(s.source) 95 return false 96 } 97 } 98 99 return true 100 } 101 102 // AnyMach returns whether any elements of this stream match the provided predicate. 103 // May not evaluate the predicate on all elements if not necessary for determining the result. 104 // If the stream is empty then false is returned and the predicate is not evaluated. 105 func (s Stream) AnyMach(predicate func(item interface{}) bool) bool { 106 for item := range s.source { 107 if predicate(item) { 108 // make sure the former goroutine not block, and current func returns fast. 109 go drain(s.source) 110 return true 111 } 112 } 113 114 return false 115 } 116 117 // Buffer buffers the items into a queue with size n. 118 // It can balance the producer and the consumer if their processing throughput don't match. 119 func (s Stream) Buffer(n int) Stream { 120 if n < 0 { 121 n = 0 122 } 123 124 source := make(chan interface{}, n) 125 go func() { 126 for item := range s.source { 127 source <- item 128 } 129 close(source) 130 }() 131 132 return Range(source) 133 } 134 135 // Concat returns a Stream that concatenated other streams 136 func (s Stream) Concat(others ...Stream) Stream { 137 source := make(chan interface{}) 138 139 go func() { 140 group := threading.NewRoutineGroup() 141 group.Run(func() { 142 for item := range s.source { 143 source <- item 144 } 145 }) 146 147 for _, each := range others { 148 each := each 149 group.Run(func() { 150 for item := range each.source { 151 source <- item 152 } 153 }) 154 } 155 156 group.Wait() 157 close(source) 158 }() 159 160 return Range(source) 161 } 162 163 // Count counts the number of elements in the result. 164 func (s Stream) Count() (count int) { 165 for range s.source { 166 count++ 167 } 168 return 169 } 170 171 // Distinct removes the duplicated items base on the given KeyFunc. 172 func (s Stream) Distinct(fn KeyFunc) Stream { 173 source := make(chan interface{}) 174 175 threading.GoSafe(func() { 176 defer close(source) 177 178 keys := make(map[interface{}]lang.PlaceholderType) 179 for item := range s.source { 180 key := fn(item) 181 if _, ok := keys[key]; !ok { 182 source <- item 183 keys[key] = lang.Placeholder 184 } 185 } 186 }) 187 188 return Range(source) 189 } 190 191 // Done waits all upstreaming operations to be done. 192 func (s Stream) Done() { 193 drain(s.source) 194 } 195 196 // Filter filters the items by the given FilterFunc. 197 func (s Stream) Filter(fn FilterFunc, opts ...Option) Stream { 198 return s.Walk(func(item interface{}, pipe chan<- interface{}) { 199 if fn(item) { 200 pipe <- item 201 } 202 }, opts...) 203 } 204 205 // First returns the first item, nil if no items. 206 func (s Stream) First() interface{} { 207 for item := range s.source { 208 // make sure the former goroutine not block, and current func returns fast. 209 go drain(s.source) 210 return item 211 } 212 213 return nil 214 } 215 216 // ForAll handles the streaming elements from the source and no later streams. 217 func (s Stream) ForAll(fn ForAllFunc) { 218 fn(s.source) 219 // avoid goroutine leak on fn not consuming all items. 220 go drain(s.source) 221 } 222 223 // ForEach seals the Stream with the ForEachFunc on each item, no successive operations. 224 func (s Stream) ForEach(fn ForEachFunc) { 225 for item := range s.source { 226 fn(item) 227 } 228 } 229 230 // Group groups the elements into different groups based on their keys. 231 func (s Stream) Group(fn KeyFunc) Stream { 232 groups := make(map[interface{}][]interface{}) 233 for item := range s.source { 234 key := fn(item) 235 groups[key] = append(groups[key], item) 236 } 237 238 source := make(chan interface{}) 239 go func() { 240 for _, group := range groups { 241 source <- group 242 } 243 close(source) 244 }() 245 246 return Range(source) 247 } 248 249 // Head returns the first n elements in p. 250 func (s Stream) Head(n int64) Stream { 251 if n < 1 { 252 panic("n must be greater than 0") 253 } 254 255 source := make(chan interface{}) 256 257 go func() { 258 for item := range s.source { 259 n-- 260 if n >= 0 { 261 source <- item 262 } 263 if n == 0 { 264 // let successive method go ASAP even we have more items to skip 265 close(source) 266 // why we don't just break the loop, and drain to consume all items. 267 // because if breaks, this former goroutine will block forever, 268 // which will cause goroutine leak. 269 drain(s.source) 270 } 271 } 272 // not enough items in s.source, but we need to let successive method to go ASAP. 273 if n > 0 { 274 close(source) 275 } 276 }() 277 278 return Range(source) 279 } 280 281 // Last returns the last item, or nil if no items. 282 func (s Stream) Last() (item interface{}) { 283 for item = range s.source { 284 } 285 return 286 } 287 288 // Map converts each item to another corresponding item, which means it's a 1:1 model. 289 func (s Stream) Map(fn MapFunc, opts ...Option) Stream { 290 return s.Walk(func(item interface{}, pipe chan<- interface{}) { 291 pipe <- fn(item) 292 }, opts...) 293 } 294 295 // Merge merges all the items into a slice and generates a new stream. 296 func (s Stream) Merge() Stream { 297 var items []interface{} 298 for item := range s.source { 299 items = append(items, item) 300 } 301 302 source := make(chan interface{}, 1) 303 source <- items 304 close(source) 305 306 return Range(source) 307 } 308 309 // NoneMatch returns whether all elements of this stream don't match the provided predicate. 310 // May not evaluate the predicate on all elements if not necessary for determining the result. 311 // If the stream is empty then true is returned and the predicate is not evaluated. 312 func (s Stream) NoneMatch(predicate func(item interface{}) bool) bool { 313 for item := range s.source { 314 if predicate(item) { 315 // make sure the former goroutine not block, and current func returns fast. 316 go drain(s.source) 317 return false 318 } 319 } 320 321 return true 322 } 323 324 // Parallel applies the given ParallelFunc to each item concurrently with given number of workers. 325 func (s Stream) Parallel(fn ParallelFunc, opts ...Option) { 326 s.Walk(func(item interface{}, pipe chan<- interface{}) { 327 fn(item) 328 }, opts...).Done() 329 } 330 331 // Reduce is an utility method to let the caller deal with the underlying channel. 332 func (s Stream) Reduce(fn ReduceFunc) (interface{}, error) { 333 return fn(s.source) 334 } 335 336 // Reverse reverses the elements in the stream. 337 func (s Stream) Reverse() Stream { 338 var items []interface{} 339 for item := range s.source { 340 items = append(items, item) 341 } 342 // reverse, official method 343 for i := len(items)/2 - 1; i >= 0; i-- { 344 opp := len(items) - 1 - i 345 items[i], items[opp] = items[opp], items[i] 346 } 347 348 return Just(items...) 349 } 350 351 // Skip returns a Stream that skips size elements. 352 func (s Stream) Skip(n int64) Stream { 353 if n < 0 { 354 panic("n must not be negative") 355 } 356 if n == 0 { 357 return s 358 } 359 360 source := make(chan interface{}) 361 362 go func() { 363 for item := range s.source { 364 n-- 365 if n >= 0 { 366 continue 367 } else { 368 source <- item 369 } 370 } 371 close(source) 372 }() 373 374 return Range(source) 375 } 376 377 // Sort sorts the items from the underlying source. 378 func (s Stream) Sort(less LessFunc) Stream { 379 var items []interface{} 380 for item := range s.source { 381 items = append(items, item) 382 } 383 sort.Slice(items, func(i, j int) bool { 384 return less(items[i], items[j]) 385 }) 386 387 return Just(items...) 388 } 389 390 // Split splits the elements into chunk with size up to n, 391 // might be less than n on tailing elements. 392 func (s Stream) Split(n int) Stream { 393 if n < 1 { 394 panic("n should be greater than 0") 395 } 396 397 source := make(chan interface{}) 398 go func() { 399 var chunk []interface{} 400 for item := range s.source { 401 chunk = append(chunk, item) 402 if len(chunk) == n { 403 source <- chunk 404 chunk = nil 405 } 406 } 407 if chunk != nil { 408 source <- chunk 409 } 410 close(source) 411 }() 412 413 return Range(source) 414 } 415 416 // Tail returns the last n elements in p. 417 func (s Stream) Tail(n int64) Stream { 418 if n < 1 { 419 panic("n should be greater than 0") 420 } 421 422 source := make(chan interface{}) 423 424 go func() { 425 ring := collection.NewRing(int(n)) 426 for item := range s.source { 427 ring.Add(item) 428 } 429 for _, item := range ring.Take() { 430 source <- item 431 } 432 close(source) 433 }() 434 435 return Range(source) 436 } 437 438 // Walk lets the callers handle each item, the caller may write zero, one or more items base on the given item. 439 func (s Stream) Walk(fn WalkFunc, opts ...Option) Stream { 440 option := buildOptions(opts...) 441 if option.unlimitedWorkers { 442 return s.walkUnlimited(fn, option) 443 } 444 445 return s.walkLimited(fn, option) 446 } 447 448 func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream { 449 pipe := make(chan interface{}, option.workers) 450 451 go func() { 452 var wg sync.WaitGroup 453 pool := make(chan lang.PlaceholderType, option.workers) 454 455 for item := range s.source { 456 // important, used in another goroutine 457 val := item 458 pool <- lang.Placeholder 459 wg.Add(1) 460 461 // better to safely run caller defined method 462 threading.GoSafe(func() { 463 defer func() { 464 wg.Done() 465 <-pool 466 }() 467 468 fn(val, pipe) 469 }) 470 } 471 472 wg.Wait() 473 close(pipe) 474 }() 475 476 return Range(pipe) 477 } 478 479 func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream { 480 pipe := make(chan interface{}, option.workers) 481 482 go func() { 483 var wg sync.WaitGroup 484 485 for item := range s.source { 486 // important, used in another goroutine 487 val := item 488 wg.Add(1) 489 // better to safely run caller defined method 490 threading.GoSafe(func() { 491 defer wg.Done() 492 fn(val, pipe) 493 }) 494 } 495 496 wg.Wait() 497 close(pipe) 498 }() 499 500 return Range(pipe) 501 } 502 503 // UnlimitedWorkers lets the caller use as many workers as the tasks. 504 func UnlimitedWorkers() Option { 505 return func(opts *rxOptions) { 506 opts.unlimitedWorkers = true 507 } 508 } 509 510 // WithWorkers lets the caller customize the concurrent workers. 511 func WithWorkers(workers int) Option { 512 return func(opts *rxOptions) { 513 if workers < minWorkers { 514 opts.workers = minWorkers 515 } else { 516 opts.workers = workers 517 } 518 } 519 } 520 521 // buildOptions returns a rxOptions with given customizations. 522 func buildOptions(opts ...Option) *rxOptions { 523 options := newOptions() 524 for _, opt := range opts { 525 opt(options) 526 } 527 528 return options 529 } 530 531 // drain drains the given channel. 532 func drain(channel <-chan interface{}) { 533 for range channel { 534 } 535 } 536 537 // newOptions returns a default rxOptions. 538 func newOptions() *rxOptions { 539 return &rxOptions{ 540 workers: defaultWorkers, 541 } 542 }