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.
450 lines
19 KiB
450 lines
19 KiB
--===========================================================================--
|
|
-- --
|
|
-- System.Reactive.Observable --
|
|
-- --
|
|
--===========================================================================--
|
|
|
|
--===========================================================================--
|
|
-- Author : kurapica125@outlook.com --
|
|
-- URL : http://github.com/kurapica/PLoop --
|
|
-- Create Date : 2019/12/01 --
|
|
-- Update Date : 2019/12/01 --
|
|
-- Version : 1.0.0 --
|
|
--===========================================================================--
|
|
|
|
PLoop(function(_ENV)
|
|
namespace "System.Reactive"
|
|
|
|
--- The attribute used to wrap a function or property that return operator to be an Observable, so could be re-used
|
|
__Sealed__() class "__Observable__" (function(_ENV)
|
|
extend "IInitAttribute"
|
|
|
|
export {
|
|
_PropertyMap = Toolset.newtable(true),
|
|
|
|
pairs = pairs,
|
|
error = error,
|
|
type = type,
|
|
rawget = rawget,
|
|
rawset = rawset,
|
|
strlower = string.lower,
|
|
safeset = Toolset.safeset,
|
|
isSubType = Class.IsSubType,
|
|
|
|
AttributeTargets, Class, Property, Event, __Observable__, Observable, BehaviorSubject
|
|
}
|
|
|
|
local _StaticSubjects = Toolset.newtable(true)
|
|
|
|
local function getSubject(prop, obj, withCreation)
|
|
local subject
|
|
|
|
if prop:IsStatic() then
|
|
subject = _StaticSubjects[prop]
|
|
|
|
if not subject and withCreation then
|
|
local stype = _PropertyMap[prop] or BehaviorSubject
|
|
subject = stype()
|
|
|
|
-- Special settings for the Behavior Subjects
|
|
if isSubType(stype, BehaviorSubject) and prop:IsReadable() and not prop:IsIndexer() then
|
|
subject:OnNext(prop:GetOwner()[prop:GetName()])
|
|
end
|
|
|
|
_StaticSubjects = safeset(_StaticSubjects, prop, subject)
|
|
end
|
|
else
|
|
local container = obj and rawget(obj, __Observable__)
|
|
subject = container and container[prop]
|
|
|
|
if not subject and obj and withCreation then
|
|
local stype = _PropertyMap[prop] or BehaviorSubject
|
|
subject = stype()
|
|
|
|
if not container then
|
|
container = {}
|
|
rawset(obj, __Observable__, container)
|
|
end
|
|
|
|
container[prop] = subject
|
|
|
|
-- Special settings for the Behavior Subjects
|
|
if isSubType(stype, BehaviorSubject) and prop:IsReadable() and not prop:IsIndexer() then
|
|
subject:OnNext(obj[prop:GetName()])
|
|
end
|
|
end
|
|
end
|
|
|
|
return subject
|
|
end
|
|
|
|
-----------------------------------------------------------
|
|
-- static method --
|
|
-----------------------------------------------------------
|
|
__Static__() function IsObservableProperty(prop)
|
|
return _PropertyMap[prop] ~= nil
|
|
end
|
|
|
|
__Static__()
|
|
function GetPropertyObservable(prop, obj)
|
|
return _PropertyMap[prop] ~= nil and getSubject(prop, obj, true) or nil
|
|
end
|
|
|
|
-----------------------------------------------------------
|
|
-- method --
|
|
-----------------------------------------------------------
|
|
function InitDefinition(self, target, targettype, definition, owner, name, stack)
|
|
if targettype == AttributeTargets.Property then
|
|
local set, handler
|
|
|
|
for k, v in pairs(definition) do
|
|
if type(k) == "string" then
|
|
local lk = strlower(k)
|
|
|
|
if lk == "auto" then
|
|
error("The property " .. name .. " can't be used as observable", stack + 1)
|
|
elseif lk == "set" then
|
|
if v == false then error("The property " .. name .. " can't be used as observable", stack + 1) end
|
|
set = k
|
|
elseif lk == "setmethod" then
|
|
set = set or k
|
|
elseif lk == "handler" then
|
|
handler = k
|
|
end
|
|
end
|
|
end
|
|
|
|
if set then
|
|
-- Replace the set
|
|
local oset = definition[set]
|
|
if type(oset) == "function" then
|
|
definition[set] = function(obj, ...)
|
|
oset(obj, ...)
|
|
|
|
local subject = getSubject(target, obj)
|
|
if subject then subject:OnNext(...) end
|
|
end
|
|
elseif type(oset) == "string" then
|
|
definition[set] = function(obj, ...)
|
|
local func = obj[oset]
|
|
if type(func) == "function" then func(obj, ...) end
|
|
|
|
local subject = getSubject(target, obj)
|
|
if subject then subject:OnNext(...) end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Replace the handler
|
|
local ohandler = definition[handler]
|
|
if type(ohandler) == "function" then
|
|
definition[handler] = function(obj, new, old, prop)
|
|
ohandler(obj, new, old, prop)
|
|
|
|
local subject = getSubject(target, obj)
|
|
if subject then subject:OnNext(new) end
|
|
end
|
|
elseif type(ohandler) == "string" then
|
|
definition[handler] = function(obj, new, old, prop)
|
|
local func = obj[ohandler]
|
|
if type(func) == "function" then func(obj, new, old, prop) end
|
|
|
|
local subject = getSubject(target, obj)
|
|
if subject then subject:OnNext(new) end
|
|
end
|
|
else
|
|
definition.handler = function(obj, new, old, prop)
|
|
local subject = getSubject(target, obj)
|
|
if subject then subject:OnNext(new) end
|
|
end
|
|
end
|
|
|
|
_PropertyMap[target] = self.SubjectType or false
|
|
else
|
|
return function(...) return Observable.Defer(target, ...) end
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------
|
|
-- property --
|
|
-----------------------------------------------------------
|
|
--- the attribute target
|
|
property "AttributeTarget" { type = AttributeTargets, default = AttributeTargets.Method + AttributeTargets.Function + AttributeTargets.Property }
|
|
|
|
property "Priority" { type = AttributePriority, default = AttributePriority.Lower }
|
|
|
|
property "SubLevel" { type = Number, default = -9999 }
|
|
|
|
property "SubjectType" { type = -Subject }
|
|
|
|
-----------------------------------------------------------
|
|
-- constructor --
|
|
-----------------------------------------------------------
|
|
__Arguments__{ -Subject/nil }
|
|
function __ctor(self, type)
|
|
self.SubjectType = type
|
|
end
|
|
end)
|
|
|
|
__Sealed__() class "Observable" (function(_ENV)
|
|
export {
|
|
Observer, Observable, IObservable, Subject, List, __Observable__,
|
|
|
|
tostring = tostring,
|
|
select = select,
|
|
unpack = _G.unpack or table.unpack,
|
|
pcall = pcall,
|
|
rawset = rawset,
|
|
loadsnippet = Toolset.loadsnippet,
|
|
IsObjectType = Class.IsObjectType,
|
|
Exception = System.Exception,
|
|
RunAsync = Threading.RunAsync,
|
|
|
|
getProperty = Class.GetFeature,
|
|
getObjectClass = Class.GetObjectClass,
|
|
}
|
|
|
|
-----------------------------------------------------------------------
|
|
-- static methods - Creating Observables --
|
|
-----------------------------------------------------------------------
|
|
--- Creates a new Observable
|
|
__Static__() __Arguments__{ Callable }
|
|
function Create(subscribe)
|
|
return Observable(subscribe)
|
|
end
|
|
|
|
--- Creates a new objservable with initstate, condition checker, iterate and a result selector
|
|
__Static__() __Arguments__{ Any, Callable, Callable, Callable/nil}
|
|
function Generate(init, condition, iterate, resultselector)
|
|
return Observable(function(observer)
|
|
local value = init
|
|
if resultselector then
|
|
while value ~= nil and condition(value) do
|
|
observer:OnNext(resultselector(value))
|
|
if observer.IsUnsubscribed then return end
|
|
value = iterate(value)
|
|
end
|
|
else
|
|
while value ~= nil and condition(value) do
|
|
observer:OnNext(value)
|
|
if observer.IsUnsubscribed then return end
|
|
value = iterate(value)
|
|
end
|
|
end
|
|
observer:OnCompleted()
|
|
end)
|
|
end
|
|
|
|
--- Returns an Observable that just provide one value
|
|
local _JustAutoGen = setmetatable({
|
|
[0] = function() return Observable(function(observer) observer:OnCompleted() end) end
|
|
}, {
|
|
__index = function(self, count)
|
|
local args = List(count):Map("i=>'arg' .. i"):Join(",")
|
|
local func = loadsnippet([[
|
|
return function(]] .. args .. [[)
|
|
return Observable(function(observer)
|
|
observer:OnNext(]] .. args .. [[)
|
|
observer:OnCompleted()
|
|
end)
|
|
end
|
|
]], "Just_Gen_" .. count, _ENV)()
|
|
rawset(self, count, func)
|
|
return func
|
|
end
|
|
}
|
|
)
|
|
__Static__() function Just(...)
|
|
return _JustAutoGen[select("#", ...)](...)
|
|
end
|
|
__Static__() Return = Just
|
|
|
|
--- Returns and Observable that immediately completes without producing a value
|
|
__Static__() function Empty()
|
|
return Observable(function(observer)
|
|
observer:OnCompleted()
|
|
end)
|
|
end
|
|
|
|
--- Returns an Observable that never produces values and never completes
|
|
__Static__() function Never()
|
|
return Observable(function() end)
|
|
end
|
|
|
|
--- Returns an Observable that immediately produces an error
|
|
__Static__() function Throw(exception)
|
|
if not (exception and IsObjectType(exception, Exception)) then
|
|
exception = Exception(exception and tostring(exception) or "Unknown error")
|
|
end
|
|
return Observable(function(observer) return observer:OnError(exception) end)
|
|
end
|
|
|
|
--- Creates the Observable only when the observer subscribes
|
|
local _DeferAutoGen = setmetatable({
|
|
[0] = function(ctor)
|
|
return Observable(function(observer)
|
|
local obs = ctor()
|
|
if not IsObjectType(obs, IObservable) then
|
|
observer:OnError(Exception("The defer function doesn't provide valid observable"))
|
|
else
|
|
return obs:Subscribe(observer)
|
|
end
|
|
end)
|
|
end,
|
|
}, {
|
|
__index = function(self, count)
|
|
local args = List(count):Map("i=>'arg' .. i"):Join(",")
|
|
local func = loadsnippet([[
|
|
return function(ctor, ]] .. args .. [[)
|
|
return Observable(function(observer)
|
|
local obs = ctor(]] .. args .. [[)
|
|
if not IsObjectType(obs, IObservable) then
|
|
observer:OnError(Exception("The defer function doesn't provide valid observable"))
|
|
else
|
|
return obs:Subscribe(observer)
|
|
end
|
|
end)
|
|
end
|
|
]], "Defer_Gen_" .. count, _ENV)()
|
|
rawset(self, count, func)
|
|
return func
|
|
end
|
|
}
|
|
)
|
|
__Static__() __Arguments__{ Callable, Any * 0 }
|
|
function Defer(ctor, ...)
|
|
return _DeferAutoGen[select("#", ...)](ctor, ...)
|
|
end
|
|
|
|
--- Converts collection objects into Observables
|
|
__Static__() __Arguments__{ Iterable }
|
|
function From(iter)
|
|
return Observable(function(observer)
|
|
for key, value in iter:GetIterator() do
|
|
if observer.IsUnsubscribed then return end
|
|
if value == nil then value, key = key, nil end
|
|
observer:OnNext(value, key)
|
|
end
|
|
observer:OnCompleted()
|
|
end)
|
|
end
|
|
|
|
--- Converts event delegate objects into Observables
|
|
__Static__() __Arguments__{ Delegate }
|
|
function From(delegate)
|
|
local subject = delegate[Observable]
|
|
if not subject then
|
|
subject = Subject()
|
|
delegate = delegate + function(...) return subject:OnNext(...) end
|
|
delegate[Observable] = subject
|
|
end
|
|
return subject
|
|
end
|
|
|
|
--- Converts tables into Observables
|
|
__Static__() __Arguments__{ Table, Callable/nil }
|
|
function From(table, iter)
|
|
return Observable(function(observer)
|
|
for key, value in (iter or pairs)(table) do
|
|
if observer.IsUnsubscribed then return end
|
|
if value == nil then value, key = key, nil end
|
|
observer:OnNext(value, key)
|
|
end
|
|
observer:OnCompleted()
|
|
end)
|
|
end
|
|
|
|
--- Create a subject based on observable property
|
|
__Static__() __Arguments__{ PropertyType }
|
|
function From(prop)
|
|
return __Observable__.GetPropertyObservable(prop, nil, true)
|
|
end
|
|
|
|
__Arguments__{ InterfaceType + ClassType, String }
|
|
__Static__() function From(class, name)
|
|
local prop = getProperty(class, name)
|
|
return prop and __Observable__.GetPropertyObservable(prop, nil, true)
|
|
end
|
|
|
|
__Static__() __Arguments__{ Table, String }
|
|
function From(self, name)
|
|
local cls = getObjectClass(self)
|
|
local prop = cls and getProperty(cls, name, true)
|
|
|
|
return prop and __Observable__.GetPropertyObservable(prop, self, true)
|
|
end
|
|
|
|
--- Creates an Observable that emits a particular range of sequential integers
|
|
__Static__() __Arguments__{ Number, Number, Number/nil }
|
|
function Range(start, stop, step)
|
|
return Observable(function(observer)
|
|
for i = start, stop, step or 1 do
|
|
if observer.IsUnsubscribed then return end
|
|
observer:OnNext(i)
|
|
end
|
|
observer:OnCompleted()
|
|
end)
|
|
end
|
|
|
|
--- Creates an Observable that emits a particular item multiple times
|
|
local _RepeatGen = setmetatable({}, {
|
|
__index = function(self, count)
|
|
local args = List(count):Map("i=>'arg' .. i"):Join(",")
|
|
local func = loadsnippet([[
|
|
return function(count, ]] .. args .. [[)
|
|
return Observable(function(observer)
|
|
local i = 0
|
|
|
|
while i < count do
|
|
if observer.IsUnsubscribed then return end
|
|
observer:OnNext(]] .. args .. [[)
|
|
i = i + 1
|
|
end
|
|
observer:OnCompleted()
|
|
end)
|
|
end
|
|
]], "Repeat_Gen_" .. count, _ENV)()
|
|
rawset(self, count, func)
|
|
return func
|
|
end
|
|
}
|
|
)
|
|
__Static__() __Arguments__{ Number, Any * 1 }
|
|
function Repeat(count, ...)
|
|
return _RepeatGen[select("#", ...)](count, ...)
|
|
end
|
|
|
|
--- Creates an Observable that emits the return value of a function-like directive
|
|
local _StartGen = setmetatable({
|
|
[0] = function(func)
|
|
return Observable(function(observer)
|
|
RunAsync(function()
|
|
observer:OnNext(func())
|
|
observer:OnCompleted()
|
|
end)
|
|
end)
|
|
end
|
|
}, {
|
|
__index = function(self, count)
|
|
local args = List(count):Map("i=>'arg' .. i"):Join(",")
|
|
local func = loadsnippet([[
|
|
return function(func, ]] .. args .. [[)
|
|
return Observable(function(observer)
|
|
RunAsync(function()
|
|
observer:OnNext(func(]] .. args .. [[))
|
|
observer:OnCompleted()
|
|
end)
|
|
end)
|
|
end
|
|
]], "Start_Gen_" .. count, _ENV)()
|
|
rawset(self, count, func)
|
|
return func
|
|
end
|
|
}
|
|
)
|
|
__Static__() __Arguments__{ Callable, Any * 0 }
|
|
function Start(func, ...)
|
|
return _StartGen[select("#", ...)](func, ...)
|
|
end
|
|
end)
|
|
end)
|
|
|