You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1866 lines
75 KiB
1866 lines
75 KiB
|
5 years ago
|
--===========================================================================--
|
||
|
|
-- --
|
||
|
|
-- System.Reactive.Operator --
|
||
|
|
-- --
|
||
|
|
--===========================================================================--
|
||
|
|
|
||
|
|
--===========================================================================--
|
||
|
|
-- Author : kurapica125@outlook.com --
|
||
|
|
-- URL : http://github.com/kurapica/PLoop --
|
||
|
|
-- Create Date : 2019/12/04 --
|
||
|
|
-- Update Date : 2020/11/23 --
|
||
|
|
-- Version : 1.0.1 --
|
||
|
|
--===========================================================================--
|
||
|
|
|
||
|
|
PLoop(function(_ENV)
|
||
|
|
namespace "System.Reactive"
|
||
|
|
|
||
|
|
--- The basic observable sequence operator
|
||
|
|
__Sealed__() class "Operator" (function(_ENV)
|
||
|
|
extend "System.IObservable" "System.IObserver"
|
||
|
|
|
||
|
|
export { Observable, Observer, Operator }
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- property --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- The observable that provides the items
|
||
|
|
property "Observable" { type = IObservable }
|
||
|
|
|
||
|
|
--- The observer that will consume the items
|
||
|
|
property "Observer" { type = IObserver }
|
||
|
|
|
||
|
|
--- The operator that controls the refer observable sequence
|
||
|
|
property "RefOperator" { type = Operator }
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- method --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
local function subscribe(self, observer)
|
||
|
|
-- Normally the Operator can't be re-used
|
||
|
|
if self.IsUnsubscribed then return end
|
||
|
|
|
||
|
|
self.Observer = observer
|
||
|
|
|
||
|
|
local onUnsubscribe
|
||
|
|
onUnsubscribe = function()
|
||
|
|
observer.OnUnsubscribe = observer.OnUnsubscribe - onUnsubscribe
|
||
|
|
self:Unsubscribe()
|
||
|
|
end
|
||
|
|
observer.OnUnsubscribe = observer.OnUnsubscribe + onUnsubscribe
|
||
|
|
|
||
|
|
self.Observable:Subscribe(self)
|
||
|
|
if self.RefOperator then self.RefOperator:Subscribe(observer) end
|
||
|
|
|
||
|
|
return observer
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Notifies the provider that an observer is to receive notifications.
|
||
|
|
__Arguments__{ IObserver }
|
||
|
|
Subscribe = subscribe
|
||
|
|
|
||
|
|
__Arguments__{ Callable/nil, Callable/nil, Callable/nil }
|
||
|
|
function Subscribe(self, onNext, onError, onCompleted)
|
||
|
|
return subscribe(self, Observer(onNext, onError, onCompleted))
|
||
|
|
end
|
||
|
|
|
||
|
|
function OnNextCore(self, ...) return self:OnNext(...) end
|
||
|
|
function OnErrorCore(self, e) return self:OnError(e) end
|
||
|
|
function OnCompletedCore(self) return self:OnCompleted() end
|
||
|
|
|
||
|
|
--- Provides the observer with new data
|
||
|
|
function OnNext(self, ...) return not self.IsUnsubscribed and self.OnNextCore(self.Observer, ...) end
|
||
|
|
|
||
|
|
--- Notifies the observer that the provider has experienced an error condition
|
||
|
|
function OnError(self, exception) return not self.IsUnsubscribed and self.OnErrorCore(self.Observer, exception) end
|
||
|
|
|
||
|
|
--- Notifies the observer that the provider has finished sending push-based notifications
|
||
|
|
function OnCompleted(self) return not self.IsUnsubscribed and self.OnCompletedCore(self.Observer) end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- constructor --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
__Arguments__{ IObservable, Callable/nil, Callable/nil, Callable/nil, Operator/nil }
|
||
|
|
function __ctor(self, observable, onNext, onError, onCompleted, refOperator)
|
||
|
|
self.Observable = observable
|
||
|
|
self.OnNextCore = onNext
|
||
|
|
self.OnErrorCore = onError
|
||
|
|
self.OnCompletedCore= onCompleted
|
||
|
|
self.RefOperator = refOperator
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- Method Extension
|
||
|
|
interface (IObservable) (function(_ENV)
|
||
|
|
export {
|
||
|
|
max = math.max,
|
||
|
|
min = math.min,
|
||
|
|
huge = math.huge,
|
||
|
|
floor = math.floor,
|
||
|
|
type = type,
|
||
|
|
tostring = tostring,
|
||
|
|
tonumber = tonumber,
|
||
|
|
tinsert = table.insert,
|
||
|
|
pcall = pcall,
|
||
|
|
next = next,
|
||
|
|
select = select,
|
||
|
|
pairs = pairs,
|
||
|
|
strformat = string.format,
|
||
|
|
loadsnippet = Toolset.loadsnippet,
|
||
|
|
tostringall = Toolset.tostringall,
|
||
|
|
otime = _G.os and (os.clock or os.time),
|
||
|
|
unpack = _G.unpack or table.unpack,
|
||
|
|
isObjectType = Class.IsObjectType,
|
||
|
|
running = coroutine.running,
|
||
|
|
yield = coroutine.yield,
|
||
|
|
resume = coroutine.resume,
|
||
|
|
status = coroutine.status,
|
||
|
|
|
||
|
|
Info = Logger.Default[Logger.LogLevel.Info],
|
||
|
|
|
||
|
|
RunWithLock = ILockManager.RunWithLock,
|
||
|
|
|
||
|
|
LOCK_KEY = "PLOOP_RX_%s",
|
||
|
|
|
||
|
|
Operator, IObservable, Observer, Observable, Threading, Guid, Exception, Dictionary, Queue, List,
|
||
|
|
PublishSubject, ReplaySubject, Subject
|
||
|
|
}
|
||
|
|
|
||
|
|
local function safeNext(observer, flag, item, ...)
|
||
|
|
if flag and item ~= nil then
|
||
|
|
return observer:OnNext(item, ...)
|
||
|
|
else
|
||
|
|
return observer:OnError(Exception(item or "No value returned"))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Tool --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- Dump the sequence
|
||
|
|
__Arguments__{ NEString/"Dump" }
|
||
|
|
function Dump(self, name)
|
||
|
|
self:Subscribe(function(...)
|
||
|
|
Info("%s-->%s", name, List{ tostringall(...) }:Join(", "))
|
||
|
|
end, function(ex)
|
||
|
|
Info("%s failed-->%s", name, tostring(ex))
|
||
|
|
end, function()
|
||
|
|
Info("%s completed", name)
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Process all elements as they arrived, works like the Subscribe, but will block the current coroutine
|
||
|
|
__Arguments__{ Callable, Callable/nil, Callable/nil }
|
||
|
|
function ForEach(self, onNext, onError, onCompleted)
|
||
|
|
local curr, main = running()
|
||
|
|
if not curr or main then return self:Subscribe(onNext, onError, onCompleted) end
|
||
|
|
|
||
|
|
local finished
|
||
|
|
|
||
|
|
self:Subscribe(onNext, function(ex)
|
||
|
|
finished = true
|
||
|
|
if onError then onError(ex) end
|
||
|
|
return resume(curr)
|
||
|
|
end, function()
|
||
|
|
finished = true
|
||
|
|
if onCompleted then onCompleted() end
|
||
|
|
return resume(curr)
|
||
|
|
end)
|
||
|
|
|
||
|
|
if not finished then return yield() end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Convert the observable sequence to an iterator, must be used in a coroutine
|
||
|
|
function ToIterator(self)
|
||
|
|
local curr, main = running()
|
||
|
|
if not curr or main then error("Usage: ToIterator(self) - must be processed in a coroutine", 2) end
|
||
|
|
local inited, finished
|
||
|
|
local queue = Queue()
|
||
|
|
|
||
|
|
return function()
|
||
|
|
if not inited then
|
||
|
|
inited = true
|
||
|
|
|
||
|
|
self:Subscribe(function(...)
|
||
|
|
queue(select("#", ...), ...)
|
||
|
|
if status(curr) == "suspended" then
|
||
|
|
resume(curr, queue:Dequeue(queue:Dequeue()))
|
||
|
|
end
|
||
|
|
end, function(ex)
|
||
|
|
finished= ex
|
||
|
|
if status(curr) == "suspended" then
|
||
|
|
resume(curr, nil, ex)
|
||
|
|
end
|
||
|
|
end, function()
|
||
|
|
finished= true
|
||
|
|
if status(curr) == "suspended" then
|
||
|
|
resume(curr)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
local count = queue:Dequeue()
|
||
|
|
if count ~= nil then
|
||
|
|
return queue:Dequeue(count)
|
||
|
|
elseif finished then
|
||
|
|
return nil, finished ~= true and finished or nil
|
||
|
|
else
|
||
|
|
return yield()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Encapsulate the sequence as a new observable sequence, so the outside can't
|
||
|
|
-- access the real sequece directly
|
||
|
|
function AsObservable(self)
|
||
|
|
return Observable(function(observer) return self:Subscribe(observer) end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Invokes actions with side effecting behavior for each element in the observable sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, Callable/nil, Callable/nil }
|
||
|
|
function Do(self, onNext, onError, onCompleted)
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
onNext(...)
|
||
|
|
return observer:OnNext(...)
|
||
|
|
end,
|
||
|
|
onError and function(observer, ex)
|
||
|
|
onError(ex)
|
||
|
|
return observer:OnError(ex)
|
||
|
|
end,
|
||
|
|
onCompleted and function(observer)
|
||
|
|
onCompleted()
|
||
|
|
return observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Catch the exception and replace with another sequence if provided
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable }
|
||
|
|
function Catch(self, onError)
|
||
|
|
return Operator(self, nil, function(observer, ex)
|
||
|
|
local ok, ret = pcall(onError, ex)
|
||
|
|
if not (ok and isObjectType(ret, IObservable)) then
|
||
|
|
observer:OnError(ex)
|
||
|
|
else
|
||
|
|
return ret:Subscribe(observer)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Process operations when the sequence is completed, error or unsubscribed
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable }
|
||
|
|
function Finally(self, finally)
|
||
|
|
local oper = Operator(self)
|
||
|
|
oper.OnUnsubscribe = oper.OnUnsubscribe + finally
|
||
|
|
return oper
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Start the next observable sequence when the sequence is failed or completed
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable }
|
||
|
|
function OnErrorResumeNext(self, observable)
|
||
|
|
local resume = function(observer) observable:Subscribe(observer) end
|
||
|
|
return Operator(self, nil, resume, resume)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Retry the sequence if failed
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ NaturalNumber/nil }
|
||
|
|
function Retry(self, count)
|
||
|
|
count = count or huge
|
||
|
|
local times = 0
|
||
|
|
return Operator(self, nil, function(observer, ex)
|
||
|
|
times = times + 1
|
||
|
|
|
||
|
|
if times > count then
|
||
|
|
observer:OnError(ex)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Filter --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- Applying a filter to a sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, Boolean/nil }
|
||
|
|
function Where(self, condition, safe)
|
||
|
|
if safe then
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
local ok, ret = pcall(condition, ...)
|
||
|
|
if not ok then return observer:OnError(Exception(ret)) end
|
||
|
|
if ret then return observer:OnNext(...) end
|
||
|
|
end)
|
||
|
|
else
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
if condition(...) then return observer:OnNext(...) end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
Filter = Where -- Alias
|
||
|
|
|
||
|
|
--- Applying a filter that only allow distinct items
|
||
|
|
__Arguments__{ Callable/nil, Boolean/nil }
|
||
|
|
__Observable__()
|
||
|
|
function Distinct(self, selector, safe)
|
||
|
|
local cache = {}
|
||
|
|
if selector then
|
||
|
|
if safe then
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
local ok, ret = pcall(selector, ...)
|
||
|
|
if not (ok and ret ~= nil) then return observer:OnError(Exception(ret or "There is no value can be used for distinct")) end
|
||
|
|
|
||
|
|
if ret ~= nil and not cache[ret] then
|
||
|
|
cache[ret] = true
|
||
|
|
return observer:OnNext(...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
else
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
local ret = selector(...)
|
||
|
|
|
||
|
|
if ret ~= nil and not cache[ret] then
|
||
|
|
cache[ret] = true
|
||
|
|
return observer:OnNext(...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
else
|
||
|
|
return Operator(self, function(observer, ret, ...)
|
||
|
|
if ret ~= nil and not cache[ret] then
|
||
|
|
cache[ret] = true
|
||
|
|
return observer:OnNext(ret, ...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Applying a filter that only value diff from the previous can pass
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable/nil, Boolean/nil }
|
||
|
|
function DistinctUntilChanged(self, selector, safe)
|
||
|
|
local previous
|
||
|
|
if selector then
|
||
|
|
if safe then
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
local ok, ret = pcall(selector, ...)
|
||
|
|
if not (ok and ret ~= nil) then return observer:OnError(Exception(ret or "There is no value can be used for distinct")) end
|
||
|
|
|
||
|
|
if ret ~= nil and ret ~= previous then
|
||
|
|
previous = ret
|
||
|
|
return observer:OnNext(...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
else
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
local ret = selector(...)
|
||
|
|
|
||
|
|
if ret ~= nil and ret ~= previous then
|
||
|
|
previous = ret
|
||
|
|
return observer:OnNext(...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
else
|
||
|
|
return Operator(self, function(observer, ret, ...)
|
||
|
|
if ret ~= nil and ret ~= previous then
|
||
|
|
previous = ret
|
||
|
|
return observer:OnNext(ret, ...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Ignored all elements, only receive complete or error notifications
|
||
|
|
__Observable__()
|
||
|
|
function IgnoreElements(self)
|
||
|
|
return self:Where("=>false")
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Skip the given count elements
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Number }
|
||
|
|
function Skip(self, count)
|
||
|
|
return self:Where(function() count = count - 1 return count < 0 end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Only take the elements of the given count
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Number }
|
||
|
|
function Take(self, count)
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
if count > 0 then
|
||
|
|
count = count - 1
|
||
|
|
observer:OnNext(...)
|
||
|
|
|
||
|
|
if count <= 0 then
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
else
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- filter out all values until a value fails the predicate, then the remaining sequence can be returned
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable }
|
||
|
|
function SkipWhile(self, condition)
|
||
|
|
local take = false
|
||
|
|
return self:Where(function(...) take = take or not condition(...) return take end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- return all values while the predicate passes, and when the first value fails the sequence will complete
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, Boolean/nil }
|
||
|
|
function TakeWhile(self, condition, safe)
|
||
|
|
if safe then
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
local ok, ret = pcall(condition, ...)
|
||
|
|
if not ok then return observer:OnError(Exception(ret)) end
|
||
|
|
|
||
|
|
if ret then
|
||
|
|
observer:OnNext(...)
|
||
|
|
else
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
else
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
if condition(...) then
|
||
|
|
observer:OnNext(...)
|
||
|
|
else
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Skip the last elements of the given count
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Number }
|
||
|
|
function SkipLast(self, last)
|
||
|
|
local count = 0
|
||
|
|
local queue = Queue()
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
count = count + 1
|
||
|
|
queue:Enqueue(select("#", ...), ...)
|
||
|
|
|
||
|
|
if count > last then
|
||
|
|
observer:OnNext(queue:Dequeue(queue:Dequeue()))
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Take the last elements of the given count
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Number }
|
||
|
|
function TakeLast(self, last)
|
||
|
|
local queue = Queue()
|
||
|
|
local count = 0
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
count = count + 1
|
||
|
|
queue:Enqueue(select("#", ...), ...)
|
||
|
|
|
||
|
|
if count > last then
|
||
|
|
queue:Dequeue(queue:Dequeue())
|
||
|
|
end
|
||
|
|
end, nil, function(observer)
|
||
|
|
local count = queue:Dequeue()
|
||
|
|
while count do
|
||
|
|
observer:OnNext(queue:Dequeue(count))
|
||
|
|
count = queue:Dequeue()
|
||
|
|
end
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Skip all values until any value is produced by a secondary observable sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable }
|
||
|
|
function SkipUntil(self, other)
|
||
|
|
local flag = false
|
||
|
|
other:Take(1):Subscribe(function() flag = true end)
|
||
|
|
|
||
|
|
return self:Where(function() return flag end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Take all values until any value is produced by a secondary observable sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable }
|
||
|
|
function TakeUntil(self, other)
|
||
|
|
local flag = true
|
||
|
|
other:Take(1):Subscribe(function() flag = false end)
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
if flag then
|
||
|
|
observer:OnNext(...)
|
||
|
|
else
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Take all values that match the prefix elements
|
||
|
|
local _MatchPrefixGen = setmetatable({}, {
|
||
|
|
__index = function(self, count)
|
||
|
|
|
||
|
|
local func = loadsnippet([[
|
||
|
|
return function(self, ]] .. List(count):Map("i=>'arg' .. i"):Join(",") .. [[)
|
||
|
|
return Operator(self, function(observer, ]] .. List(count):Map("i=>'brg' .. i"):Join(",") .. [[, ...)
|
||
|
|
if ]] .. List(count):Map("i=>'arg'..i..' == brg' .. i"):Join(" and ") .. [[ then
|
||
|
|
return observer:OnNext(]] .. List(count):Map("i=>'brg' .. i"):Join(",") .. [[, ...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
]], "MatchPrefix_Gen_" .. count, _ENV)()
|
||
|
|
rawset(self, count, func)
|
||
|
|
return func
|
||
|
|
end
|
||
|
|
}
|
||
|
|
)
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ System.Any * 1 }
|
||
|
|
function MatchPrefix(self, ...)
|
||
|
|
return _MatchPrefixGen[select("#", ...)](self, ...)
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Inspection --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- Returns a single value sequence indicate whether the target observable sequence contains any value or value meet the predicate
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable/nil, Boolean/nil }
|
||
|
|
function Any(self, predicate, safe)
|
||
|
|
return Operator(self, predicate and (
|
||
|
|
safe and function(observer, ...)
|
||
|
|
local ok, ret = pcall(predicate, ...)
|
||
|
|
if not ok then return observer:OnError(Exception(ret)) end
|
||
|
|
|
||
|
|
if ret then
|
||
|
|
observer:OnNext(true)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end or function(observer, ...)
|
||
|
|
if predicate(...) then
|
||
|
|
observer:OnNext(true)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end) or function(observer, ...)
|
||
|
|
observer:OnNext(true)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end,
|
||
|
|
nil, function(observer)
|
||
|
|
observer:OnNext(false)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a single value sequence indicate whether the target observable sequence's all values meet the predicate
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, Boolean/nil }
|
||
|
|
function All(self, predicate, safe)
|
||
|
|
return Operator(self, safe and function(observer, ...)
|
||
|
|
local ok, ret = pcall(predicate, ...)
|
||
|
|
if not ok then return observer:OnError(Exception(ret)) end
|
||
|
|
|
||
|
|
if not ret then
|
||
|
|
observer:OnNext(false)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end or function(observer, ...)
|
||
|
|
if not predicate(...) then
|
||
|
|
observer:OnNext(false)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end, nil, function(observer)
|
||
|
|
observer:OnNext(true)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a single value sequence indicate whether the target observable sequence contains a specific value
|
||
|
|
local _ContainsGen = setmetatable({}, {
|
||
|
|
__index = function(self, count)
|
||
|
|
|
||
|
|
local func = loadsnippet([[
|
||
|
|
return function(self, ]] .. List(count):Map("i=>'arg' .. i"):Join(",") .. [[)
|
||
|
|
return self:Any(function(]] .. List(count):Map("i=>'brg' .. i"):Join(",") .. [[)
|
||
|
|
return ]] .. List(count):Map("i=>'arg'..i..' == brg' .. i"):Join(" and ") .. [[
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
]], "Contains_Gen_" .. count, _ENV)()
|
||
|
|
rawset(self, count, func)
|
||
|
|
return func
|
||
|
|
end
|
||
|
|
}
|
||
|
|
)
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ System.Any * 1 }
|
||
|
|
function Contains(self, ...)
|
||
|
|
return _ContainsGen[select("#", ...)](self, ...)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with single default value if the target observable sequence doesn't contains any item
|
||
|
|
local _DefaultGen = setmetatable({}, {
|
||
|
|
__index = function(self, count)
|
||
|
|
local func = loadsnippet([[
|
||
|
|
return function(self, ]] .. List(count):Map("i=>'arg' .. i"):Join(",") .. [[)
|
||
|
|
local e = false
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
e = true
|
||
|
|
observer:OnNext(...)
|
||
|
|
end, nil, function(observer)
|
||
|
|
if not e then
|
||
|
|
observer:OnNext(]] .. List(count):Map("i=>'arg' .. i"):Join(",") .. [[)
|
||
|
|
end
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
]], "Default_Gen_" .. count, _ENV)()
|
||
|
|
rawset(self, count, func)
|
||
|
|
return func
|
||
|
|
end
|
||
|
|
}
|
||
|
|
)
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ System.Any * 1 }
|
||
|
|
function Default(self, ...)
|
||
|
|
return _DefaultGen[select("#", ...)](self, ...)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Raise exception if the sequence don't provide any elements
|
||
|
|
__Observable__()
|
||
|
|
function NotEmpty(self)
|
||
|
|
local hasElements = false
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
hasElements = true
|
||
|
|
return observer:OnNext(...)
|
||
|
|
end, nil, function(observer)
|
||
|
|
if not hasElements then
|
||
|
|
return observer:OnError(Exception("The sequence doesn't provide any elements"))
|
||
|
|
else
|
||
|
|
return observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with the value at the given index(0-base) of the target observable sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ NaturalNumber }
|
||
|
|
function ElementAt(self, index)
|
||
|
|
index = floor(index)
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
if index == 0 then
|
||
|
|
observer:OnNext(...)
|
||
|
|
observer:OnCompleted()
|
||
|
|
else
|
||
|
|
index = index - 1
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Compares two observable sequences whether those sequences has the same values in the same order and that the sequences are the same length
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable, Callable/"x,y=>x==y" }
|
||
|
|
function SequenceEqual(self, other, compare)
|
||
|
|
local fqueue = Queue()
|
||
|
|
local squeue = Queue()
|
||
|
|
local iscomp
|
||
|
|
|
||
|
|
local compareQueue = function(observer)
|
||
|
|
local fcount = fqueue:Dequeue()
|
||
|
|
local scount = squeue:Dequeue()
|
||
|
|
|
||
|
|
if fcount ~= scount then
|
||
|
|
observer:OnNext(false)
|
||
|
|
return observer:OnCompleted()
|
||
|
|
end
|
||
|
|
|
||
|
|
for i = 1, fcount do
|
||
|
|
if not compare(fqueue:Dequeue(), squeue:Dequeue()) then
|
||
|
|
observer:OnNext(false)
|
||
|
|
return observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local complete = function(observer)
|
||
|
|
if iscomp then
|
||
|
|
while not observer.IsUnsubscribed and squeue:Peek() and fqueue:Peek() do
|
||
|
|
compareQueue(observer)
|
||
|
|
end
|
||
|
|
|
||
|
|
if not observer.IsUnsubscribed then
|
||
|
|
if squeue:Peek() or fqueue:Peek() then
|
||
|
|
observer:OnNext(false)
|
||
|
|
else
|
||
|
|
observer:OnNext(true)
|
||
|
|
end
|
||
|
|
return observer:OnCompleted()
|
||
|
|
end
|
||
|
|
else
|
||
|
|
iscomp = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return Operator(self,
|
||
|
|
function(observer, ...)
|
||
|
|
fqueue(select("#", ...), ...)
|
||
|
|
return squeue:Peek() and compareQueue(observer)
|
||
|
|
end, nil, complete,
|
||
|
|
-- The other sequence
|
||
|
|
Operator(other,
|
||
|
|
function(observer, ...)
|
||
|
|
squeue(select("#", ...), ...)
|
||
|
|
return fqueue:Peek() and compareQueue(observer)
|
||
|
|
end, nil, complete
|
||
|
|
)
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Aggregation --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- Returns a sequence with a single value generated from the source sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, System.Any/nil, Boolean/nil }
|
||
|
|
function Aggregate(self, accumulator, seed, safe)
|
||
|
|
return Operator(self, safe and function(observer, ...)
|
||
|
|
if seed == nil then
|
||
|
|
seed = ...
|
||
|
|
else
|
||
|
|
local ok, ret = pcall(accumulator, seed, ...)
|
||
|
|
if not ok then observer:OnError(Exception(ret)) end
|
||
|
|
seed = ret
|
||
|
|
end
|
||
|
|
end or function(observer, ...)
|
||
|
|
if seed == nil then
|
||
|
|
seed = ...
|
||
|
|
else
|
||
|
|
seed = accumulator(seed, ...)
|
||
|
|
end
|
||
|
|
end, nil, function(observer)
|
||
|
|
if seed ~= nil then observer:OnNext(seed) end
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with a single value being the count of the values in the source sequence
|
||
|
|
__Observable__()
|
||
|
|
function Count(self)
|
||
|
|
return self:Aggregate(function(seed, item) return seed + 1 end, 0)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with a single value being the min value of the source sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable/"x,y=>x<y" }
|
||
|
|
function Min(self, compare)
|
||
|
|
return self:Aggregate(function(seed, item) if compare(item, seed) then return item else return seed end end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with a single value being the max value of the source sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable/"x,y=>x<y" }
|
||
|
|
function Max(self, compare)
|
||
|
|
return self:Aggregate(function(seed, item) if compare(item, seed) then return seed else return item end end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with a single value being the sum value of the source sequence
|
||
|
|
__Observable__()
|
||
|
|
function Sum(self)
|
||
|
|
return self:Aggregate(function(seed, item) return seed + tonumber(item) end, 0)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with a single value being the average value of the source sequence
|
||
|
|
__Observable__()
|
||
|
|
function Average(self)
|
||
|
|
return self:Aggregate(function(seed, item) seed[1], seed[2] = seed[1] + tonumber(item), seed[2] + 1 return seed end, { 0, 0 })
|
||
|
|
:Map(function(seed) if seed[2] > 0 then return seed[1]/seed[2] end end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with a single value being the first value of the source sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable/nil, Boolean/nil }
|
||
|
|
function First(self, predicate, safe)
|
||
|
|
return Operator(self, predicate and (safe and function(observer, ...)
|
||
|
|
local ok, ret = pcall(predicate, ...)
|
||
|
|
if not ok then return observer:OnError(Exception(ret)) end
|
||
|
|
|
||
|
|
if ret then
|
||
|
|
observer:OnNext(...)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end or function(observer, ...)
|
||
|
|
if predicate(...) then
|
||
|
|
observer:OnNext(...)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end) or function(observer, ...)
|
||
|
|
observer:OnNext(...)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with a single value being the last value of the source sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable/nil, Boolean/nil }
|
||
|
|
function Last(self, predicate, safe)
|
||
|
|
local last = {}
|
||
|
|
|
||
|
|
return Operator(self, predicate and (safe and function(observer, ...)
|
||
|
|
local ok, ret = pcall(predicate, ...)
|
||
|
|
if not ok then return observer:OnError(Exception(ret)) end
|
||
|
|
|
||
|
|
if ret then
|
||
|
|
last[0] = select("#", ...)
|
||
|
|
for i = 1, last[0] do last[i] = select(i, ...) end
|
||
|
|
end
|
||
|
|
end or function(observer, ...)
|
||
|
|
if predicate(...) then
|
||
|
|
last[0] = select("#", ...)
|
||
|
|
for i = 1, last[0] do last[i] = select(i, ...) end
|
||
|
|
end
|
||
|
|
end) or function(observer, ...)
|
||
|
|
last[0] = select("#", ...)
|
||
|
|
for i = 1, last[0] do last[i] = select(i, ...) end
|
||
|
|
end, nil, function(observer)
|
||
|
|
if last[0] then observer:OnNext(unpack(last, 1, last[0])) end
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a sequence with calculated values from the source sequence,
|
||
|
|
-- if emits the seed, the first value will be used as the seed
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, System.Any/nil, Boolean/nil }
|
||
|
|
function Scan(self, accumulator, seed, safe)
|
||
|
|
return Operator(self, safe and function(observer, ...)
|
||
|
|
if seed == nil then
|
||
|
|
seed = ...
|
||
|
|
else
|
||
|
|
local ok, ret = pcall(accumulator, seed, ...)
|
||
|
|
if not ok then observer:OnError(Exception(ret)) end
|
||
|
|
seed = ret
|
||
|
|
observer:OnNext(seed)
|
||
|
|
end
|
||
|
|
end or function(observer, ...)
|
||
|
|
if seed == nil then
|
||
|
|
seed = ...
|
||
|
|
else
|
||
|
|
seed = accumulator(seed, ...)
|
||
|
|
observer:OnNext(seed)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Partitioning --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- Returns a sequence with groups generated by the source sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, Boolean/nil }
|
||
|
|
function GroupBy(self, selector, safe)
|
||
|
|
local groups = Dictionary()
|
||
|
|
|
||
|
|
return Operator(self, safe and function(observer, ...)
|
||
|
|
local ok, key = pcall(selector, ...)
|
||
|
|
if not (ok and key ~= nil) then
|
||
|
|
local ex = Exception(key or "The key selector doesn't return a value")
|
||
|
|
groups.Values:Each("OnError", ex)
|
||
|
|
return observer:OnError(ex)
|
||
|
|
end
|
||
|
|
|
||
|
|
if key ~= nil then
|
||
|
|
local group = groups[key]
|
||
|
|
if not group then
|
||
|
|
group = Subject()
|
||
|
|
group.Key = key
|
||
|
|
groups[key] = group
|
||
|
|
observer:OnNext(group)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
group:OnNext(...)
|
||
|
|
end or function(observer, ...)
|
||
|
|
local key = selector(...)
|
||
|
|
if key ~= nil then
|
||
|
|
local group = groups[key]
|
||
|
|
if not group then
|
||
|
|
group = Subject()
|
||
|
|
group.Key = key
|
||
|
|
groups[key] = group
|
||
|
|
observer:OnNext(group)
|
||
|
|
end
|
||
|
|
|
||
|
|
group:OnNext(...)
|
||
|
|
end
|
||
|
|
end,
|
||
|
|
function(observer, exception)
|
||
|
|
groups.Values:Each("OnError", exception)
|
||
|
|
observer:OnError(exception)
|
||
|
|
end,
|
||
|
|
function(observer)
|
||
|
|
groups.Values:Each("OnCompleted")
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns an observable sequence containing a list of zero or more elements that have a minimum key value
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, Callable/"x,y=>x<y" }
|
||
|
|
function MinBy(self, selector, compare)
|
||
|
|
local result = List()
|
||
|
|
local minkey
|
||
|
|
return Operator(self:GroupBy(selector), function(observer, group)
|
||
|
|
local key = group.Key
|
||
|
|
if minkey == nil or compare(key, minkey) then
|
||
|
|
minkey = key
|
||
|
|
result:Clear()
|
||
|
|
end
|
||
|
|
|
||
|
|
group:Subscribe(function(...)
|
||
|
|
if minkey == key then
|
||
|
|
if select("#", ...) > 1 then
|
||
|
|
result:Insert({...})
|
||
|
|
else
|
||
|
|
result:Insert(...)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end, nil, function(observer)
|
||
|
|
observer:OnNext(result)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns an observable sequence containing a list of zero or more elements that have a maximum key value
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, Callable/"x,y=>x<y" }
|
||
|
|
function MaxBy(self, selector, compare)
|
||
|
|
local result = List()
|
||
|
|
local maxkey
|
||
|
|
return Operator(self:GroupBy(selector), function(observer, group)
|
||
|
|
local key = group.Key
|
||
|
|
if maxkey == nil or compare(maxkey, key) then
|
||
|
|
maxkey = key
|
||
|
|
result:Clear()
|
||
|
|
end
|
||
|
|
|
||
|
|
group:Subscribe(function(...)
|
||
|
|
if maxkey == key then
|
||
|
|
if select("#", ...) > 1 then
|
||
|
|
result:Insert({...})
|
||
|
|
else
|
||
|
|
result:Insert(...)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end, nil, function(observer)
|
||
|
|
observer:OnNext(result)
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Transformation --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- Returns an observable sequence with elements converted from the the
|
||
|
|
-- source sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable, Boolean/nil }
|
||
|
|
function Select(self, selector, safe)
|
||
|
|
return Operator(self, safe and function(observer, ...)
|
||
|
|
return safeNext(observer, pcall(selector, ...))
|
||
|
|
end or function(observer, ...)
|
||
|
|
return observer:OnNext(selector(...))
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
Map = Select
|
||
|
|
|
||
|
|
--- Convert the source sequence's elements into several observable
|
||
|
|
-- sequence, then combined those child sequence to produce a final sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Callable }
|
||
|
|
function SelectMany(self, selector)
|
||
|
|
local childobs = {}
|
||
|
|
local iscompleted
|
||
|
|
|
||
|
|
return Operator(self,
|
||
|
|
function(observer, ...)
|
||
|
|
local ok,ret= pcall(selector, ...)
|
||
|
|
if not (ok and isObjectType(ret, IObservable)) then
|
||
|
|
local ex= Exception(ret or "The key selector doesn't return an observable sequence")
|
||
|
|
for key in pairs(childobs) do
|
||
|
|
key:Unsubscribe()
|
||
|
|
end
|
||
|
|
return observer:OnError(ex)
|
||
|
|
end
|
||
|
|
|
||
|
|
local obs
|
||
|
|
obs = Observer(function(...)
|
||
|
|
observer:OnNext(...)
|
||
|
|
end, function(ex)
|
||
|
|
for key in pairs(childobs) do
|
||
|
|
key:Unsubscribe()
|
||
|
|
end
|
||
|
|
observer:OnError(ex)
|
||
|
|
end, function()
|
||
|
|
childobs[obs] = nil
|
||
|
|
if not next(childobs) and iscompleted then
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
childobs[obs] = true
|
||
|
|
ret:Subscribe(obs)
|
||
|
|
end,
|
||
|
|
function(observer, ex)
|
||
|
|
for key in pairs(childobs) do
|
||
|
|
key:Unsubscribe()
|
||
|
|
end
|
||
|
|
observer:OnError(ex)
|
||
|
|
end,
|
||
|
|
function(observer)
|
||
|
|
iscompleted = true
|
||
|
|
if not next(childobs) then observer:OnCompleted() end
|
||
|
|
end
|
||
|
|
)
|
||
|
|
end
|
||
|
|
FlatMap = SelectMany
|
||
|
|
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ NEString, Boolean/nil }
|
||
|
|
function Format(self, fmt, safe)
|
||
|
|
return Operator(self, safe and function(observer, ...)
|
||
|
|
return safeNext(observer, pcall(strformat, fmt, ...))
|
||
|
|
end or function(observer, ...)
|
||
|
|
local ok, res = pcall(strformat, fmt, ...)
|
||
|
|
return ok and observer:OnNext(res)
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Combining --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- Concatenates two or more observable sequences. Returns an observable
|
||
|
|
-- sequence that contains the elements of the first sequence, followed by
|
||
|
|
-- those of the second the sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable * 1 }
|
||
|
|
function Concat(self, ...)
|
||
|
|
if select("#", ...) == 1 then
|
||
|
|
local observable= ...
|
||
|
|
return Operator(self, nil, nil, function(observer) observable:Subscribe(observer) end)
|
||
|
|
else
|
||
|
|
local queue = Queue{ ... }
|
||
|
|
|
||
|
|
local oper
|
||
|
|
oper = Operator(self, nil, nil, function(observer)
|
||
|
|
local nxt = queue:Dequeue()
|
||
|
|
if nxt then
|
||
|
|
return nxt:Subscribe(oper)
|
||
|
|
else
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
return oper
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Repeats the observable sequence indefinitely and sequentially
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ NaturalNumber/nil }
|
||
|
|
function Repeat(self, count)
|
||
|
|
count = count or huge
|
||
|
|
local times = 0
|
||
|
|
local oper
|
||
|
|
oper = Operator(self, nil, nil, function(observer)
|
||
|
|
times = times + 1
|
||
|
|
if times < count then
|
||
|
|
return self:Subscribe(oper)
|
||
|
|
else
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
return oper
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Prefix values to a sequence
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable }
|
||
|
|
function StartWith(self, observable)
|
||
|
|
return Operator(observable, nil, nil, function(observer)
|
||
|
|
return self:Subscribe(observer)
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ System.Any * 2 }
|
||
|
|
function StartWith(self, ...)
|
||
|
|
return Operator(Observable.From(List{ ... }), nil, nil, function(observer)
|
||
|
|
return self:Subscribe(observer)
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ System.Any }
|
||
|
|
function StartWith(self, val)
|
||
|
|
return Operator(Observable.Just(val), nil, nil, function(observer)
|
||
|
|
return self:Subscribe(observer)
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Return values from the sequence that is first to produce values, and ignore the other sequences
|
||
|
|
__Observable__()
|
||
|
|
__Static__() __Arguments__{ IObservable * 2 }
|
||
|
|
function Observable.Amb(...)
|
||
|
|
local observables = { ... }
|
||
|
|
local choosed
|
||
|
|
local subjects = {}
|
||
|
|
local rcompleted
|
||
|
|
|
||
|
|
return Operator(Observable.Range(1, #observables),
|
||
|
|
function(observer, index)
|
||
|
|
if choosed then return end
|
||
|
|
local subject
|
||
|
|
subject = Subject(observables[index], function(nobserver, ...)
|
||
|
|
if choosed == subject then
|
||
|
|
nobserver:OnNext(...)
|
||
|
|
elseif not choosed then
|
||
|
|
choosed = subject
|
||
|
|
for o in pairs(subjects) do
|
||
|
|
if o ~= subject then o:Unsubscribe() end
|
||
|
|
end
|
||
|
|
nobserver:OnNext(...)
|
||
|
|
else
|
||
|
|
subject:Unsubscribe()
|
||
|
|
end
|
||
|
|
end, function(nobserver, ex)
|
||
|
|
if choosed == subject then
|
||
|
|
nobserver:OnError(ex)
|
||
|
|
elseif not choosed then
|
||
|
|
subjects[subject] = nil
|
||
|
|
if rcompleted and not next(subjects) then
|
||
|
|
nobserver:OnError(ex)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end, function(nobserver)
|
||
|
|
if choosed == subject then
|
||
|
|
nobserver:OnCompleted()
|
||
|
|
elseif not choosed then
|
||
|
|
subjects[subject] = nil
|
||
|
|
if rcompleted and not next(subjects) then
|
||
|
|
nobserver:OnCompleted()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
)
|
||
|
|
|
||
|
|
subjects[subject] = true
|
||
|
|
subject:Subscribe(observer)
|
||
|
|
end,
|
||
|
|
function(observer, ex)
|
||
|
|
rcompleted = true
|
||
|
|
if not next(subjects) then
|
||
|
|
observer:OnError(ex)
|
||
|
|
end
|
||
|
|
end,
|
||
|
|
function(observer)
|
||
|
|
rcompleted = true
|
||
|
|
if not next(subjects) then
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
)
|
||
|
|
end
|
||
|
|
Amb = Observable.Amb
|
||
|
|
|
||
|
|
--- Merge multi sequence, their results will be merged as the result sequence
|
||
|
|
__Observable__()
|
||
|
|
__Static__() __Arguments__{ IObservable * 2 }
|
||
|
|
function Observable.Merge(...)
|
||
|
|
local observables = { ... }
|
||
|
|
local subjects = {}
|
||
|
|
local rcompleted
|
||
|
|
|
||
|
|
return Operator(Observable.Range(1, #observables),
|
||
|
|
function(observer, index)
|
||
|
|
local subject
|
||
|
|
subject = Subject(observables[index], nil, nil, function(nobserver)
|
||
|
|
subjects[subject] = nil
|
||
|
|
if rcompleted and not next(subjects) then
|
||
|
|
nobserver:OnCompleted()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
)
|
||
|
|
|
||
|
|
subjects[subject] = true
|
||
|
|
subject:Subscribe(observer)
|
||
|
|
end,
|
||
|
|
function(observer, ex)
|
||
|
|
rcompleted = true
|
||
|
|
if not next(subjects) then
|
||
|
|
observer:OnError(ex)
|
||
|
|
end
|
||
|
|
end,
|
||
|
|
function(observer)
|
||
|
|
rcompleted = true
|
||
|
|
if not next(subjects) then
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
)
|
||
|
|
end
|
||
|
|
Merge = Observable.Merge
|
||
|
|
|
||
|
|
--- Switch will subscribe to the outer sequence and as each inner sequence is
|
||
|
|
-- yielded it will subscribe to the new inner sequence and dispose of the subscription to the previous inner sequence
|
||
|
|
__Observable__()
|
||
|
|
__Static__() __Arguments__{ IObservable * 2 }
|
||
|
|
function Observable.Switch(...)
|
||
|
|
local observables = { ... }
|
||
|
|
local choosed = 0
|
||
|
|
local subjects = {}
|
||
|
|
local rcompleted
|
||
|
|
|
||
|
|
return Operator(Observable.Range(1, #observables),
|
||
|
|
function(observer, index)
|
||
|
|
local subject
|
||
|
|
subject = Subject(observables[index], function(nobserver, ...)
|
||
|
|
if choosed == index then
|
||
|
|
nobserver:OnNext(...)
|
||
|
|
elseif choosed < index then
|
||
|
|
if choosed > 0 then
|
||
|
|
subjects[choosed]:Unsubscribe()
|
||
|
|
subjects[choosed] = nil
|
||
|
|
end
|
||
|
|
choosed = index
|
||
|
|
nobserver:OnNext(...)
|
||
|
|
else
|
||
|
|
subjects[index] = nil
|
||
|
|
subject:Unsubscribe()
|
||
|
|
end
|
||
|
|
end, function(nobserver, ex)
|
||
|
|
if index >= choosed then
|
||
|
|
for k, v in pairs(subjects) do
|
||
|
|
v:Unsubscribe()
|
||
|
|
end
|
||
|
|
nobserver:OnError(ex)
|
||
|
|
end
|
||
|
|
end, function(nobserver)
|
||
|
|
subjects[index] = nil
|
||
|
|
if rcompleted and not next(subjects) then
|
||
|
|
nobserver:OnCompleted()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
)
|
||
|
|
|
||
|
|
subjects[index] = subject
|
||
|
|
subject:Subscribe(observer)
|
||
|
|
end,
|
||
|
|
function(observer, ex)
|
||
|
|
rcompleted = true
|
||
|
|
if not next(subjects) then
|
||
|
|
observer:OnError(ex)
|
||
|
|
end
|
||
|
|
end,
|
||
|
|
function(observer)
|
||
|
|
rcompleted = true
|
||
|
|
if not next(subjects) then
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
)
|
||
|
|
end
|
||
|
|
Switch = Observable.Switch
|
||
|
|
|
||
|
|
--- The CombineLatest extension method allows you to take the most recent value from two sequences, and with a given
|
||
|
|
--- function transform those into a value for the result sequence
|
||
|
|
__Arguments__{ IObservable, Callable/nil }
|
||
|
|
function CombineLatest(self, secseq, resultSelector)
|
||
|
|
return Observable(function(observer)
|
||
|
|
local cache = {}
|
||
|
|
local start, stop
|
||
|
|
local completed
|
||
|
|
|
||
|
|
local complete = function(observer)
|
||
|
|
if completed then
|
||
|
|
observer:OnCompleted()
|
||
|
|
else
|
||
|
|
completed = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local process = resultSelector and function(observer)
|
||
|
|
if start and stop then
|
||
|
|
safeNext(observer, pcall(resultSelector, unpack(cache, start, stop)))
|
||
|
|
end
|
||
|
|
end or function(observer)
|
||
|
|
if start and stop then
|
||
|
|
observer:OnNext(unpack(cache, start, stop))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
Subject(self, function(observer, ...)
|
||
|
|
local count = select("#", ...)
|
||
|
|
start = 1
|
||
|
|
|
||
|
|
for i = 0, count - 1 do
|
||
|
|
start = start - 1
|
||
|
|
cache[start] = select(count - i, ...)
|
||
|
|
end
|
||
|
|
|
||
|
|
return process(observer)
|
||
|
|
end, nil, complete):Subscribe(observer)
|
||
|
|
|
||
|
|
Subject(secseq, function(observer, ...)
|
||
|
|
local count = select("#", ...)
|
||
|
|
stop = count
|
||
|
|
for i = 1, count do
|
||
|
|
cache[i]= select(i, ...)
|
||
|
|
end
|
||
|
|
return process(observer)
|
||
|
|
end, nil, complete):Subscribe(observer)
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- the Zip method brings together two sequences of values as pairs
|
||
|
|
__Arguments__{ IObservable, Callable/nil }
|
||
|
|
function Zip(self, secseq, resultSelector)
|
||
|
|
return Observable(function(observer)
|
||
|
|
local queuea = Queue()
|
||
|
|
local queueb = Queue()
|
||
|
|
local completed
|
||
|
|
|
||
|
|
local complete = function(observer)
|
||
|
|
if completed then
|
||
|
|
observer:OnCompleted()
|
||
|
|
else
|
||
|
|
completed = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
Subject(self, function(observer, ...)
|
||
|
|
local count = select("#", ...)
|
||
|
|
local second= queueb:Dequeue()
|
||
|
|
if second == nil then
|
||
|
|
queuea:Enqueue(count, ...)
|
||
|
|
else
|
||
|
|
if count == 1 then
|
||
|
|
local a1= ...
|
||
|
|
if resultSelector then
|
||
|
|
safeNext(observer, pcall(resultSelector, a1, queueb:Dequeue(second)))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, queueb:Dequeue(second))
|
||
|
|
end
|
||
|
|
elseif count == 2 then
|
||
|
|
local a1, a2 = ...
|
||
|
|
if resultSelector then
|
||
|
|
safeNext(observer, pcall(resultSelector, a1, a2, queueb:Dequeue(second)))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, a2, queueb:Dequeue(second))
|
||
|
|
end
|
||
|
|
elseif count == 3 then
|
||
|
|
local a1, a2, a3 = ...
|
||
|
|
if resultSelector then
|
||
|
|
safeNext(observer, pcall(resultSelector, a1, a2, a3, queueb:Dequeue(second)))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, a2, a3, queueb:Dequeue(second))
|
||
|
|
end
|
||
|
|
else
|
||
|
|
local q = Queue():Enqueue(...):Enqueue(queueb:Dequeue(second))
|
||
|
|
if resultSelector then
|
||
|
|
safeNext(observer, pcall(resultSelector, q:Dequeue(q.Count)))
|
||
|
|
else
|
||
|
|
observer:OnNext(q:Dequeue(q.Count))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end, nil, complete):Subscribe(observer)
|
||
|
|
|
||
|
|
Subject(secseq, function(observer, ...)
|
||
|
|
local count = select("#", ...)
|
||
|
|
local first = queuea:Dequeue()
|
||
|
|
if first == nil then
|
||
|
|
queueb:Enqueue(count, ...)
|
||
|
|
else
|
||
|
|
if first == 1 then
|
||
|
|
local a1= queuea:Dequeue(first)
|
||
|
|
if resultSelector then
|
||
|
|
safeNext(observer, pcall(resultSelector, a1, ...))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, ...)
|
||
|
|
end
|
||
|
|
elseif first == 2 then
|
||
|
|
local a1, a2 = queuea:Dequeue(first)
|
||
|
|
if resultSelector then
|
||
|
|
safeNext(observer, pcall(resultSelector, a1, a2, ...))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, a2, ...)
|
||
|
|
end
|
||
|
|
elseif first == 3 then
|
||
|
|
local a1, a2, a3 = queuea:Dequeue(first)
|
||
|
|
if resultSelector then
|
||
|
|
safeNext(observer, pcall(resultSelector, a1, a2, a3, ...))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, a2, a3, ...)
|
||
|
|
end
|
||
|
|
else
|
||
|
|
local q = Queue():Enqueue(queuea:Dequeue(first)):Enqueue(...)
|
||
|
|
if resultSelector then
|
||
|
|
safeNext(observer, pcall(resultSelector, q:Dequeue(q.Count)))
|
||
|
|
else
|
||
|
|
observer:OnNext(q:Dequeue(q.Count))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end, nil, complete):Subscribe(observer)
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- combine items emitted by two Observables whenever an item from one
|
||
|
|
-- Observable is emitted during a time window defined according to an
|
||
|
|
-- item emitted by the other Observable
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable, Callable, Callable, Callable/nil }
|
||
|
|
function Join(self, right, leftDurationSelector, rightDurationSelector, resultSelector)
|
||
|
|
local leftwindows = List()
|
||
|
|
local rightwindows = List()
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
local count = select("#", ...)
|
||
|
|
|
||
|
|
-- join the right window
|
||
|
|
if #rightwindows > 0 then
|
||
|
|
if count == 1 then
|
||
|
|
local a1 = ...
|
||
|
|
for _, rwin in rightwindows:GetIterator() do
|
||
|
|
if resultSelector then
|
||
|
|
observer:OnNext(resultSelector(a1, unpack(rwin)))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, unpack(rwin))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
elseif count == 2 then
|
||
|
|
local a1, a2 = ...
|
||
|
|
for _, rwin in rightwindows:GetIterator() do
|
||
|
|
if resultSelector then
|
||
|
|
observer:OnNext(resultSelector(a1, a2, unpack(rwin)))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, a2, unpack(rwin))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
elseif count == 3 then
|
||
|
|
local a1, a2, a3 = ...
|
||
|
|
for _, rwin in rightwindows:GetIterator() do
|
||
|
|
if resultSelector then
|
||
|
|
observer:OnNext(resultSelector(a1, a2, a3, unpack(rwin)))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, a2, a3, unpack(rwin))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
else
|
||
|
|
for _, rwin in rightwindows:GetIterator() do
|
||
|
|
local queue = Queue{ ... }:Enqueue(unpack(rwin))
|
||
|
|
if resultSelector then
|
||
|
|
observer:OnNext(resultSelector(queue:Dequeue(queue.Count)))
|
||
|
|
else
|
||
|
|
observer:OnNext(queue:Dequeue(queue.Count))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Open window
|
||
|
|
local ok, selector = pcall(leftDurationSelector, ...)
|
||
|
|
if not ok then return observer:OnError(Exception(selector)) end
|
||
|
|
if not isObjectType(selector, IObservable) then return observer:OnError(Exception("The selector doesn't return a valid value")) end
|
||
|
|
|
||
|
|
local window
|
||
|
|
|
||
|
|
local close = function()
|
||
|
|
window:Unsubscribe()
|
||
|
|
leftwindows:Remove(window)
|
||
|
|
end
|
||
|
|
|
||
|
|
window = Observer(close, close, close)
|
||
|
|
|
||
|
|
for i = 1, count do
|
||
|
|
window[i]= select(i, ...)
|
||
|
|
end
|
||
|
|
|
||
|
|
leftwindows:Insert(window)
|
||
|
|
selector:Subscribe(window)
|
||
|
|
end, function(observer, ex)
|
||
|
|
leftwindows:Each("Unsubscribe")
|
||
|
|
rightwindows:Each("Unsubscribe")
|
||
|
|
observer:OnError(ex)
|
||
|
|
end, function(observer)
|
||
|
|
leftwindows:Each("Unsubscribe")
|
||
|
|
rightwindows:Each("Unsubscribe")
|
||
|
|
observer:OnCompleted()
|
||
|
|
end, Operator(right, function(observer, ...)
|
||
|
|
local count = select("#", ...)
|
||
|
|
|
||
|
|
-- join the right window
|
||
|
|
if #leftwindows > 0 then
|
||
|
|
for _, lwin in leftwindows:GetIterator() do
|
||
|
|
local lcnt = #lwin
|
||
|
|
if lcnt == 1 then
|
||
|
|
local a1 = lwin[1]
|
||
|
|
if resultSelector then
|
||
|
|
observer:OnNext(resultSelector(a1, ...))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, ...)
|
||
|
|
end
|
||
|
|
elseif lcnt == 2 then
|
||
|
|
local a1, a2 = lwin[1], lwin[2]
|
||
|
|
if resultSelector then
|
||
|
|
observer:OnNext(resultSelector(a1, a2, ...))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, a2, ...)
|
||
|
|
end
|
||
|
|
elseif lcnt == 3 then
|
||
|
|
local a1, a2, a3 = lwin[1], lwin[2], lwin[3]
|
||
|
|
if resultSelector then
|
||
|
|
observer:OnNext(resultSelector(a1, a2, a3, ...))
|
||
|
|
else
|
||
|
|
observer:OnNext(a1, a2, a3, ...)
|
||
|
|
end
|
||
|
|
else
|
||
|
|
local queue = Queue{ unpack(lwin) }:Enqueue(...)
|
||
|
|
if resultSelector then
|
||
|
|
observer:OnNext(resultSelector(queue:Dequeue(queue.Count)))
|
||
|
|
else
|
||
|
|
observer:OnNext(queue:Dequeue(queue.Count))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Open window
|
||
|
|
local ok, selector = pcall(rightDurationSelector, ...)
|
||
|
|
if not ok then return observer:OnError(Exception(selector)) end
|
||
|
|
if not isObjectType(selector, IObservable) then return observer:OnError(Exception("The selector doesn't return a valid value")) end
|
||
|
|
|
||
|
|
local window
|
||
|
|
|
||
|
|
local close = function()
|
||
|
|
window:Unsubscribe()
|
||
|
|
rightwindows:Remove(window)
|
||
|
|
end
|
||
|
|
|
||
|
|
window = Observer(close, close, close)
|
||
|
|
|
||
|
|
for i = 1, count do
|
||
|
|
window[i]= select(i, ...)
|
||
|
|
end
|
||
|
|
|
||
|
|
rightwindows:Insert(window)
|
||
|
|
selector:Subscribe(window)
|
||
|
|
end, function(observer, ex)
|
||
|
|
leftwindows:Each("Unsubscribe")
|
||
|
|
rightwindows:Each("Unsubscribe")
|
||
|
|
observer:OnError(ex)
|
||
|
|
end, function(observer)
|
||
|
|
leftwindows:Each("Unsubscribe")
|
||
|
|
rightwindows:Each("Unsubscribe")
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Plan --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
local Pattern = class {}
|
||
|
|
local Plan = class {}
|
||
|
|
|
||
|
|
__Arguments__{ IObservable * 1 }
|
||
|
|
function And(self, ...)
|
||
|
|
return Pattern{ self, ... }
|
||
|
|
end
|
||
|
|
|
||
|
|
__Arguments__{ IObservable * 1 }
|
||
|
|
function Pattern:And(...)
|
||
|
|
for i = 1, select("#", ...) do
|
||
|
|
tinsert(self, (select(i, ...)))
|
||
|
|
end
|
||
|
|
return self
|
||
|
|
end
|
||
|
|
|
||
|
|
__Arguments__{ Callable/nil }
|
||
|
|
function Pattern:Then(resultSelector)
|
||
|
|
return Plan{ self, resultSelector or false }
|
||
|
|
end
|
||
|
|
|
||
|
|
-- @todo: Multi-os thread support
|
||
|
|
__Static__() __Arguments__{ Plan }
|
||
|
|
function Observable.When(plan)
|
||
|
|
return Observable(function(observer)
|
||
|
|
local sequences = plan[1]
|
||
|
|
local selector = plan[2]
|
||
|
|
local total = #sequences
|
||
|
|
local queues = List(total, function() return Queue() end)
|
||
|
|
local count = 0
|
||
|
|
local completed = 0
|
||
|
|
|
||
|
|
local process = selector and function(index)
|
||
|
|
count = count + 1
|
||
|
|
|
||
|
|
if count == total then
|
||
|
|
local rs= Queue()
|
||
|
|
|
||
|
|
queues:Each(function(queue)
|
||
|
|
rs:Enqueue(queue:Dequeue(queue:Dequeue()))
|
||
|
|
if queue:Peek() == nil then count = count - 1 end
|
||
|
|
end)
|
||
|
|
|
||
|
|
safeNext(observer, pcall(selector, rs:Dequeue(rs.Count)))
|
||
|
|
end
|
||
|
|
end or function(index)
|
||
|
|
count = count + 1
|
||
|
|
|
||
|
|
if count == total then
|
||
|
|
local rs= Queue()
|
||
|
|
|
||
|
|
queues:Each(function(queue)
|
||
|
|
rs:Enqueue(queue:Dequeue(queue:Dequeue()))
|
||
|
|
if queue:Peek() == nil then count = count - 1 end
|
||
|
|
end)
|
||
|
|
|
||
|
|
observer:OnNext(rs:Dequeue(rs.Count))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
for i = 1, total do
|
||
|
|
local j = i
|
||
|
|
Subject(sequences[i], function(_, ...)
|
||
|
|
local isnew = queues[j]:Peek() == nil
|
||
|
|
queues[j]:Enqueue(select("#", ...), ...)
|
||
|
|
if isnew then return process(j) end
|
||
|
|
end, nil, function()
|
||
|
|
completed = completed + 1
|
||
|
|
if completed == total then
|
||
|
|
observer:OnCompleted()
|
||
|
|
end
|
||
|
|
end):Subscribe(observer)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Time-shifted --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
--- The Buffer operator allows you to store away a range of values and then
|
||
|
|
-- re-publish them as a list once the buffer is full
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ NaturalNumber, NaturalNumber/nil }
|
||
|
|
function Buffer(self, total, skip)
|
||
|
|
local queue = Queue()
|
||
|
|
skip = skip or total
|
||
|
|
local skipcnt = 0
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
queue:Enqueue(...)
|
||
|
|
|
||
|
|
local qcnt = queue.Count
|
||
|
|
|
||
|
|
while qcnt > 0 do
|
||
|
|
if skipcnt > 0 then
|
||
|
|
local dcnt = qcnt < skipcnt and qcnt or skipcnt
|
||
|
|
queue:Dequeue(dcnt)
|
||
|
|
skipcnt = skipcnt - dcnt
|
||
|
|
qcnt = qcnt - dcnt
|
||
|
|
end
|
||
|
|
|
||
|
|
if qcnt >= total then
|
||
|
|
observer:OnNext(List{ queue:Peek(total) })
|
||
|
|
|
||
|
|
if qcnt >= skip then
|
||
|
|
queue:Dequeue(skip)
|
||
|
|
qcnt= qcnt - skip
|
||
|
|
else
|
||
|
|
queue:Dequeue(qcnt)
|
||
|
|
skipcnt = skip - qcnt
|
||
|
|
qcnt= 0
|
||
|
|
end
|
||
|
|
else
|
||
|
|
break
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end, nil, function(observer)
|
||
|
|
local qcnt = queue.Count
|
||
|
|
if qcnt > skipcnt then
|
||
|
|
if skipcnt > 0 then queue:Dequeue(skipcnt) end
|
||
|
|
observer:OnNext(List{ queue:Dequeue(qcnt - skipcnt) })
|
||
|
|
end
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- periodically subdivide items from an Observable into Observable windows
|
||
|
|
-- and emit these windows rather than emitting the items one at a time
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ NaturalNumber, NaturalNumber/nil }
|
||
|
|
function Window(self, total, skip)
|
||
|
|
skip = skip or total
|
||
|
|
|
||
|
|
local queue = Queue()
|
||
|
|
local skipcnt = 0
|
||
|
|
local sendcnt = 0
|
||
|
|
local currsub
|
||
|
|
local curroff = 1
|
||
|
|
local queuecnt = 0
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
queue:Enqueue(select("#", ...), ...)
|
||
|
|
queuecnt = queuecnt + 1
|
||
|
|
|
||
|
|
if skipcnt > 0 then
|
||
|
|
local dcnt = queuecnt < skipcnt and queuecnt or skipcnt
|
||
|
|
for i = 1, dcnt do
|
||
|
|
queue:Dequeue(queue:Dequeue())
|
||
|
|
end
|
||
|
|
skipcnt = skipcnt - dcnt
|
||
|
|
queuecnt = queuecnt - dcnt
|
||
|
|
|
||
|
|
if queuecnt == 0 then return end
|
||
|
|
end
|
||
|
|
|
||
|
|
if sendcnt == 0 then
|
||
|
|
if currsub then currsub:OnCompleted() end
|
||
|
|
|
||
|
|
currsub = Subject()
|
||
|
|
observer:OnNext(currsub)
|
||
|
|
end
|
||
|
|
|
||
|
|
while sendcnt < total do
|
||
|
|
local length= queue:Peek(curroff, 1)
|
||
|
|
if not length then return end
|
||
|
|
|
||
|
|
sendcnt = sendcnt + 1
|
||
|
|
currsub:OnNext(queue:Peek(curroff + 1, length))
|
||
|
|
curroff = curroff + 1 + length
|
||
|
|
end
|
||
|
|
|
||
|
|
skipcnt = skip
|
||
|
|
sendcnt = 0
|
||
|
|
curroff = 1
|
||
|
|
end, function(observer, ex)
|
||
|
|
if currsub then currsub:OnError(ex) end
|
||
|
|
observer:OnError(ex)
|
||
|
|
end, function(observer)
|
||
|
|
if currsub then currsub:OnCompleted() end
|
||
|
|
observer:OnCompleted()
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable }
|
||
|
|
function Window(self, sampler)
|
||
|
|
local currsub
|
||
|
|
|
||
|
|
local onError = function(observer, ex)
|
||
|
|
if currsub then currsub:OnError(ex) end
|
||
|
|
observer:OnError(ex)
|
||
|
|
end
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
if not currsub then
|
||
|
|
currsub = Subject()
|
||
|
|
observer:OnNext(currsub)
|
||
|
|
end
|
||
|
|
|
||
|
|
currsub:OnNext(...)
|
||
|
|
end, onError, function(observer)
|
||
|
|
if currsub then currsub:OnCompleted() end
|
||
|
|
observer:OnCompleted()
|
||
|
|
end, Operator(sampler,
|
||
|
|
function(observer, ...)
|
||
|
|
if currsub then currsub:OnCompleted() end
|
||
|
|
|
||
|
|
currsub = Subject()
|
||
|
|
observer:OnNext(currsub)
|
||
|
|
end,
|
||
|
|
onError,
|
||
|
|
function(observer) end
|
||
|
|
)
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Returns a new Observable that produces its most recent value every time
|
||
|
|
-- the specified observable produces a value
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ IObservable }
|
||
|
|
function Sample(self, sampler)
|
||
|
|
local queue = Queue()
|
||
|
|
local completed
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
queue:Clear()
|
||
|
|
queue:Enqueue(...)
|
||
|
|
end, nil, function(observer)
|
||
|
|
if completed then observer:OnCompleted() end
|
||
|
|
completed = true
|
||
|
|
end,
|
||
|
|
Operator(sampler, function(observer, ...)
|
||
|
|
local count = queue.Count
|
||
|
|
if count > 0 then
|
||
|
|
observer:OnNext(queue:Dequeue(count))
|
||
|
|
end
|
||
|
|
end, nil, function(observer)
|
||
|
|
local count = queue.Count
|
||
|
|
if count > 0 then
|
||
|
|
observer:OnNext(queue:Dequeue(count))
|
||
|
|
end
|
||
|
|
if completed then observer:OnCompleted() end
|
||
|
|
completed = true
|
||
|
|
end)
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Ignores values from an observable sequence which are followed by another value before dueTime
|
||
|
|
if otime then
|
||
|
|
__Observable__()
|
||
|
|
__Arguments__{ Number }
|
||
|
|
function Throttle(self, dueTime)
|
||
|
|
local lasttime = 0
|
||
|
|
|
||
|
|
return Operator(self, function(observer, ...)
|
||
|
|
local curr = otime()
|
||
|
|
if curr - lasttime > dueTime then
|
||
|
|
lasttime= curr
|
||
|
|
observer:OnNext(...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
Debounce = Throttle
|
||
|
|
end
|
||
|
|
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
-- Publish and Connect --
|
||
|
|
-----------------------------------------------------------------------
|
||
|
|
function Publish(self)
|
||
|
|
return PublishSubject(self)
|
||
|
|
end
|
||
|
|
|
||
|
|
__Arguments__{ NaturalNumber/nil }
|
||
|
|
function Replay(self, size)
|
||
|
|
return ReplaySubject(self, size)
|
||
|
|
end
|
||
|
|
|
||
|
|
__Arguments__{ -Subject/Subject, System.Any * 0 }
|
||
|
|
function ToSubject(self, subject, ...)
|
||
|
|
return subject(self, ...)
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end)
|