匿名
未登录
中文(中国大陆)
登录
深色模式
「荏苒之境」
搜索
查看“︁模块:Meteor”︁的源代码
来自「荏苒之境」
命名空间
模块
讨论
更多
更多
页面操作
阅读
查看源代码
历史
清除缓存
←
模块:Meteor
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
local table_clear = require('table.clear') local pop = function(t) local len = #t local r = t[len] t[len] = nil return r end local push = function(t, v) t[#t+1] = v end ---@class meteor.package local meteor = {} ---@type table[] meteor.array_pool = {} meteor.signal_scope_pool = {} meteor.scheduler_cache_pool = {} --#region signal system ---@alias meteor.signal_listener fun(signal_type: any, ...) ---@class meteor.signal_system ---@field listeners { [any]: meteor.signal_listener[] } local signal_system_mt = {} signal_system_mt.__index = signal_system_mt ---@return meteor.signal_system meteor.new_signal_system = function() return setmetatable({ listeners = {} }, signal_system_mt) end meteor.signal_system = meteor.new_signal_system() ---@param listeners? { [any]: meteor.signal_listener } function signal_system_mt:push_scope(listeners) local scope = pop(meteor.signal_scope_pool) or {} scope.listeners = listeners self[#self+1] = scope end ---@return any[] function signal_system_mt:pop_scope() local i = #self local top = self[i] self[i] = nil return top end ---@generic T ---@param f fun(): T ---@param listeners? { [any]: meteor.signal_listener } ---@return boolean success, table signals, T|string result-or-error function signal_system_mt:with_scope(f, listeners) self:push_scope(listeners) local succ, res_or_err = pcall(f) return succ, self:pop_scope(), res_or_err end ---@generic T ---@param signal_type T ---@param f meteor.signal_listener function signal_system_mt:register_listener(signal_type, f) local ls = self.listeners[signal_type] if ls == nil then ls = {} self.listeners[signal_type] = ls end ls[#ls+1] = f end ---@generic T ---@param signal_type T ---@param f meteor.signal_listener ---@return boolean function signal_system_mt:unregister_listener(signal_type, f) local ls = self.listeners[signal_type] if ls == nil then return false end for i = 1, #ls do if ls[i] == f then for j = i, #ls do ls[j] = ls[j+1] end if #ls == 0 then self.listeners[signal_type] = nil end return true end end return false end ---@param signal_type any ---@param ... any function signal_system_mt:trigger(signal_type, ...) if signal_type == nil then error("#1 signal_type cannot be nil") end local signal = { signal_type, ... } local top = self[#self] if top ~= nil then top[#top+1] = signal local ls = top.listeners if ls ~= nil then local l = ls[signal_type] if l ~= nil then l(signal) end end end local global_ls = self.listeners[signal_type] if global_ls then for i = 1, #global_ls do global_ls[i](signal) end end end --#endregion signal system --#region reactive object local SIG_REACTIVE_READ = {} local SIG_REACTIVE_WRITE = {} local KEY_REACTIVE_VALUE = {} local KEY_REACTIVE_TAG = {} local KEY_REACTIVE_DEPS = {} local KEY_REACTIVE_EXPR = {} local KEY_REACTIVE_INDEX = {} local KEY_VALUE = {} meteor.KEY_VALUE = KEY_VALUE ---@alias meteor meteor.reactive<any> ---@alias meteor.reactive_tag 'value'|'expr'|'lazy'|'table'|table ---@class meteor.reactive<T>: { value: T? } } local reactive_mt = {} function reactive_mt:__index(k) meteor.signal_system:trigger(SIG_REACTIVE_READ, self) local v = rawget(self, KEY_REACTIVE_VALUE) if k == "value" then return v else return v[k] end end function reactive_mt:__newindex(k, new) local sig_sys = meteor.signal_system local v if k == "value" then v = new rawset(self, KEY_REACTIVE_VALUE, v) sig_sys:trigger(SIG_REACTIVE_WRITE, self, v) else if k == KEY_VALUE then k = "value" end v = rawget(self, KEY_REACTIVE_VALUE) local prev = v[k] v[k] = new sig_sys:trigger(SIG_REACTIVE_WRITE, self, v, k, prev, new) end end ---@generic T ---@param initial T? ---@return meteor.reactive<T> meteor.ref = function(initial) return setmetatable({ [KEY_REACTIVE_VALUE] = initial }, reactive_mt) end ---@generic T ---@param ref meteor.reactive<T> ---@return T meteor.peek_ref_value = function(ref) return rawget(ref, KEY_REACTIVE_VALUE) end ---@param ref meteor ---@return meteor[]? meteor.get_ref_deps = function(ref) return rawget(ref, KEY_REACTIVE_DEPS) end ---@param ref meteor ---@return meteor.reactive_tag meteor.get_ref_tag = function(ref) return rawget(ref, KEY_REACTIVE_TAG) or 'value' end ---@generic T ---@param ref meteor.reactive<T> ---@return (fun(): T)? meteor.get_ref_expr = function(ref) return rawget(ref, KEY_REACTIVE_EXPR) end -- TODO: support return reactive object directly ---@generic T ---@param f fun(): T ---@return meteor[], T local function collect_reactive(f) local signal_sys = meteor.signal_system local succ, scope, res = signal_sys:with_scope(f) if not succ then error("error during reactive object collection: "..res) end local rs = pop(meteor.array_pool) or {} for i = 1, #scope do local sig = scope[i] if sig[1] ~= SIG_REACTIVE_READ then signal_sys:trigger(unpack(sig)) else rs[#rs+1] = sig[2] end end table_clear(scope) push(meteor.signal_scope_pool, scope) return rs, res end ---@generic T ---@param expr fun(): T ---@return meteor.reactive<T> meteor.compute = function(expr) local rs, v = collect_reactive(expr) local r = meteor.ref(v) rawset(r, KEY_REACTIVE_TAG, 'expr') rawset(r, KEY_REACTIVE_DEPS, rs) rawset(r, KEY_REACTIVE_EXPR, expr) return r end ---@generic T ---@param expr fun(): T ---@return meteor.reactive<T> meteor.lazy = function(expr) local rs, v = collect_reactive(expr) local r = meteor.ref(v) rawset(r, KEY_REACTIVE_TAG, 'lazy') rawset(r, KEY_REACTIVE_DEPS, rs) rawset(r, KEY_REACTIVE_EXPR, expr) return r end ---@generic T: table ---@param t T ---@return meteor.reactive<T> meteor.table = function(t) local index_map = {} local r = meteor.compute(function() local o = {} for k, v in pairs(t) do if getmetatable(v) == reactive_mt then o[k] = v.value index_map[v] = k else o[k] = v end end return o end) rawset(r, KEY_REACTIVE_TAG, 'table') rawset(r, KEY_REACTIVE_INDEX, index_map) return r end ---@generic K, V, U ---@param t table<K, V> ---@param f (fun(v: V, k?: K): meteor.reactive<U>) ---@return meteor.reactive<table<K, U>> meteor.map = function(t, f) local exprs = {} for k, v in pairs(t) do exprs[k] = meteor.compute(function() return f(v, k).value end) end local r = meteor.table(exprs) rawset(r, KEY_REACTIVE_TAG, 'map') return r end ---@alias meteor.inspect_config { --- formatter: (fun(v: any): string), --- indent_text: string, ---} ---@param t string[] ---@param r meteor ---@param indent integer ---@param config meteor.inspect_config local function do_inspect(t, r, indent, config) local indent_text = config.indent_text or ' ' for _ = 1, indent do t[#t+1] = indent_text end t[#t+1] = tostring(meteor.get_ref_tag(r)) t[#t+1] = ': ' t[#t+1] = config.formatter(meteor.peek_ref_value(r)) t[#t+1] = '\n' local deps = meteor.get_ref_deps(r) if deps ~= nil then local indent_plus = indent + 1 for i = 1, #deps do do_inspect(t, deps[i], indent_plus, config) end end end ---@param r meteor ---@param config meteor.inspect_config ---@return string meteor.inspect = function(r, config) local t = {} do_inspect(t, r, 0, config) return table.concat(t) end --#endregion reactive object --#region scheduler ---@alias meteor.scheduler.reactive_cache { --- tag: meteor.reactive_tag, --- value: any, --- downstreams: { [meteor]: true }, --- pinned: boolean, --- version: integer, --- lazy_dirty: boolean, --- is_prev: boolean, ---} ---@class meteor.scheduler ---@field caches { [meteor]: meteor.scheduler.reactive_cache } ---@field dirty_seq meteor[] ---@field dirty_seq_upstream meteor[] ---@field version integer ---@field private read_listener meteor.signal_listener ---@field private write_listener meteor.signal_listener local scheduler_mt = {} scheduler_mt.__index = scheduler_mt ---@param schd meteor.scheduler ---@param r meteor ---@return meteor.scheduler.reactive_cache local function register_reactive_to_scheduler(schd, r) local cache = schd.caches[r] if cache ~= nil then return cache end cache = pop(meteor.scheduler_cache_pool) or {} cache.tag = meteor.get_ref_tag(r) cache.value = meteor.peek_ref_value(r) cache.downstreams = {} schd.caches[r] = cache print("register: "..tostring(cache.value)) local deps = meteor.get_ref_deps(r) if deps ~= nil then for i = 1, #deps do local dep_cache = register_reactive_to_scheduler(schd, deps[i]) dep_cache.downstreams[r] = true end end return cache end ---@param schd meteor.scheduler ---@param r meteor local function unregister_reactive_from_scheduler(schd, r) print("unregister: "..meteor.peek_ref_value(r).name) local caches = schd.caches local cache = caches[r] caches[r] = nil table_clear(cache) push(meteor.scheduler_cache_pool, cache) local deps = meteor.get_ref_deps(r) if deps ~= nil then for i = 1, #deps do unregister_reactive_from_scheduler(schd, deps[i]) end end end ---@param schd meteor.scheduler ---@param r meteor ---@param upstream meteor local function mark_reactive_dirty(schd, r, upstream) local cache = schd.caches[r] if cache.tag == 'lazy' then cache.lazy_dirty = true else local d = schd.dirty_seq local du = schd.dirty_seq_upstream d[#d+1] = r du[#du+1] = upstream end end ---@param schd meteor.scheduler ---@param t table? ---@param new_t table ---@param initializer fun(cache: meteor.scheduler.reactive_cache, r: meteor, k: any, t: table, schd: meteor.scheduler) ---@param deinitializer fun(cache: meteor.scheduler.reactive_cache, r: meteor, k: any, t: table, schd: meteor.scheduler): boolean local function generic_refresh_reactive_table(schd, t, new_t, initializer, deinitializer) local caches = schd.caches if t ~= nil then for _, r in pairs(t) do local cache = caches[r] if cache then cache.is_prev = true end end end for k, r in pairs(new_t) do if getmetatable(r) == reactive_mt then local cache = register_reactive_to_scheduler(schd, r) if cache.is_prev then cache.is_prev = nil else initializer(cache, r, k, new_t, schd) end end end if t ~= nil then for k, r in pairs(t) do local cache = caches[r] if cache ~= nil and cache.is_prev then cache.is_prev = nil if deinitializer(cache, r, k, t, schd) then unregister_reactive_from_scheduler(schd, r) end end end end end ---@param schd meteor.scheduler ---@param r meteor ---@param cache meteor.scheduler.reactive_cache local function refresh_reactive_expr(schd, r, cache) local expr = meteor.get_ref_expr(r) if expr == nil then error('fatal: expr') end local prev_deps = meteor.get_ref_deps(r) local deps, v = collect_reactive(expr) rawset(r, KEY_REACTIVE_VALUE, v) rawset(r, KEY_REACTIVE_DEPS, deps) cache.value = v generic_refresh_reactive_table(schd, prev_deps, deps, function(dep_cache) dep_cache.downstreams[r] = true end, function(dep_cache) local ds = dep_cache.downstreams ds[r] = nil return next(ds) == nil and not cache.pinned end) if prev_deps ~= nil then table_clear(prev_deps) push(meteor.array_pool, prev_deps) end end ---@param schd meteor.scheduler ---@param r meteor ---@param cache meteor.scheduler.reactive_cache ---@param upstream meteor? ---@param version integer local function refresh_reactive(schd, r, cache, upstream, version) print('refresh '..cache.tag..' '..tostring(cache.value)..', upstream: '..tostring(upstream and meteor.peek_ref_value(upstream) or 'nil')) -- refresh reactive expr local tag = cache.tag if tag == 'expr' then if cache.version == version then print('---> same version!') return end cache.version = version refresh_reactive_expr(schd, r, cache) elseif tag == 'table' then local t = cache.value local index_map = rawget(r, KEY_REACTIVE_INDEX) if upstream == nil then error('upstream cannot be nil when refreshing reactive table') end local k = index_map[upstream] local new = meteor.peek_ref_value(upstream) if t[k] == new then print('---> table no change!') return end t[k] = new end local caches = schd.caches for dr in pairs(cache.downstreams) do refresh_reactive(schd, dr, caches[dr], r, version) end end ---@param schd meteor.scheduler ---@return meteor.signal_listener local function create_read_listener(schd) local caches = schd.caches return function(s) local r = s[2] local cache = caches[r] if cache == nil then return end if cache.lazy_dirty then refresh_reactive(schd, r, cache, nil, schd.version) for dr in pairs(cache.downstreams) do local dr_cache = caches[dr] if dr_cache.tag == 'lazy' then dr_cache.lazy_dirty = true else refresh_reactive(schd, r, cache, nil, schd.version) end end end end end ---@param schd meteor.scheduler ---@return meteor.signal_listener local function create_write_listener(schd) local caches = schd.caches return function(s) local r = s[2] local cache = caches[r] if cache == nil then return end local prev = cache.value local new, k = s[3], s[4] if k ~= nil and cache.tag == 'table' then local prev_v = s[5] local new_v = s[6] if prev_v == new_v then return end local index_map = rawget(r, KEY_REACTIVE_INDEX) if getmetatable(prev_v) == reactive_mt then local prev_cache = caches[prev_v] local prev_ds = prev_cache.downstreams prev_ds[r] = nil if next(prev_ds) == nil then unregister_reactive_from_scheduler(schd, prev_v) end index_map[prev_v] = nil end if getmetatable(new_v) == reactive_mt then local new_cache = register_reactive_to_scheduler(schd, new_v) new_cache.downstreams[r] = true new[k] = new_cache.value index_map[new_v] = k end elseif prev ~= new then if cache.tag == 'table' then local index_map = rawget(r, KEY_REACTIVE_INDEX) generic_refresh_reactive_table(schd, prev, new, function(v_cache, v, vk) index_map[v] = vk v_cache.downstreams[r] = true end, function(v_cache, v) index_map[v] = nil local ds = v_cache.downstreams ds[r] = nil return next(ds) == nil end) end cache.value = new else return end for dr in pairs(cache.downstreams) do mark_reactive_dirty(schd, dr, r) end end end ---@return meteor.scheduler meteor.new_scheduler = function() local t = { caches = {}, dirty_seq = {}, dirty_seq_upstream = {}, version = 0 } t.read_listener = create_read_listener(t) t.write_listener = create_write_listener(t) setmetatable(t, scheduler_mt) meteor.signal_system:register_listener(SIG_REACTIVE_READ, t.read_listener) meteor.signal_system:register_listener(SIG_REACTIVE_WRITE, t.write_listener) return t end ---@generic T ---@param expr fun(): T ---@return meteor.reactive<T> function scheduler_mt:capture(expr) local r = meteor.compute(expr) local cache = register_reactive_to_scheduler(self, r) cache.pinned = true return r end function scheduler_mt:refresh() local dirty_seq = self.dirty_seq if #dirty_seq == 0 then return end local version = self.version self.version = version + 1 local caches = self.caches local dirty_seq_upstream = self.dirty_seq_upstream for i = 1, #dirty_seq do local dr = dirty_seq[i] local cache = caches[dr] local upstream = dirty_seq_upstream[i] refresh_reactive(self, dr, cache, upstream, version) end table_clear(dirty_seq) table_clear(dirty_seq_upstream) end function scheduler_mt:dispose() meteor.signal_system:unregister_listener(SIG_REACTIVE_WRITE, self.write_listener) end return meteor
该页面使用的模板:
模块:Meteor/doc
(
查看源代码
)
返回
模块:Meteor
。
导航
导航
最近更改
随机页面
特殊页面
模板列表
wiki工具
wiki工具
Cargo数据
页面工具
页面工具
用户页面工具
更多
链入页面
相关更改
页面信息
页面日志