Open fanyipin opened 2 months ago
我设想的是service层在backend的方法例如 appendchild,replacechild等方法中发送消息到view层,view层利用原生的appendchild等方式实现
这个方向是可行的,但是不能直接将 shadow 协议的后端操作对应到原生方法上。 可以参考这个实现 https://github.com/Tidyzq/glass-easel/blob/wip-shadow-sync/glass-easel-shadow-sync/src/backend.ts
我没尝试过这么做,不过根据经验可以有两个猜测。
这个方向是可行的,但是不能直接将 shadow 协议的后端操作对应到原生方法上。 可以参考这个实现 https://github.com/Tidyzq/glass-easel/blob/wip-shadow-sync/glass-easel-shadow-sync/src/backend.ts
嗯,好的,我先学习一下
我没尝试过这么做,不过根据经验可以有两个猜测。
- 通信本身的延迟太大:这个需要你优化一下通信协议,特别是要注意下是不是需要做一些“消息合并”之类的。
- 界面更新开销太大:最终对 DOM 树的变更应该尽可能在更少的 js task 中完成,换句话说,一次 setData 调用可能会产生很多 backend 调用,这些 backend 调用应当在同一个 js task 中调用到 DOM 。
嗯,消息合并之类的操作已经做过了。后面的dom树变更优化我再想下
还有我看glass-easel要在10月1号完成 MiniProgram 环境下的 webview 后端,这个任务是指将service和view拆分到两个webview去渲染吗?是跟我上面提的事儿类似吗
这个方向是可行的,但是不能直接将 shadow 协议的后端操作对应到原生方法上。 可以参考这个实现 https://github.com/Tidyzq/glass-easel/blob/wip-shadow-sync/glass-easel-shadow-sync/src/backend.ts
@Tidyzq 抱歉,没有完全看懂这段代码,和我理解的有点儿不太一样,有几个小疑惑希望能帮忙解答一下:
我没尝试过这么做,不过根据经验可以有两个猜测。
- 通信本身的延迟太大:这个需要你优化一下通信协议,特别是要注意下是不是需要做一些“消息合并”之类的。
- 界面更新开销太大:最终对 DOM 树的变更应该尽可能在更少的 js task 中完成,换句话说,一次 setData 调用可能会产生很多 backend 调用,这些 backend 调用应当在同一个 js task 中调用到 DOM 。
嗯,消息合并之类的操作已经做过了。后面的dom树变更优化我再想下
还有我看glass-easel要在10月1号完成 MiniProgram 环境下的 webview 后端,这个任务是指将service和view拆分到两个webview去渲染吗?是跟我上面提的事儿类似吗
milestone 的 deadline 会根据实际情况调整,并不准确。 glass-easel 的 milestone 通常是项目自身的目标,和正式 landing 到 MiniProgram 环境也不是一回事。 milestone v1.0 的主要目标是接口全面稳定。
@Tidyzq 我直接引用了https://github.com/Tidyzq/glass-easel/blob/wip-shadow-sync/glass-easel-shadow-sync/src/backend.ts里面的代码,并把channel替换成了electron的ipc通信,发现还是存在上面的问题: 我的小程序代码大概如下: js:
Page({
data: {
showAgain: false,
num: 1,
list: ['tesxt', 'fjeijfioef', 'fejfiejofjeojfoie'],
renderList: ["1", "2"]
},
helloTap() {
this.setData({
showAgain: !this.data.showAgain,
num: new Date().getTime(),
renderList: []
})
this.setData({
showAgain: !this.data.showAgain,
num: new Date().getTime(),
renderList: Array.from({length: 300}, (_, i) => i + parseFloat(Math.random().toString()).toFixed(2))
})
},
})
wxml:
<view wx:for="{{renderList}}">hello-{{index}}-{{item}}</view>
<view class="hello" bind:tap="helloTap">
Hello world! {{num}}
</view>
<wx-progress></wx-progress>
在点击的时候响应很慢,这个消耗貌似不在通信,我看service从接收到点击事件到最后一次的时间间隔都有将近2s的时间,具体可见下图:
https://github.com/user-attachments/assets/610b8a9c-1fb3-4af5-8137-d97a07a02bc8
上图中右侧为service即glass-easel backend运行的webview,在点击时,我会先清空数组,然后再赋值300项,并动态渲染元素,可以看到在backend侧间隔时间就要接近2s,所以是不是我的用法有问题?
我本意是想要用glass-easel用来模拟小程序的整体架构,在electron中,backend运行在service的webview中,另一个view的webview接收service传递的消息,现在发现渲染性能并不能满足需求,是我的打开方式有问题吗?
我理解通过glass-easel可以将逻辑处理和渲染层renderer分开,这样我就可以在service中处理逻辑,view中接收service的消息进行渲染,可目前看效果不能达到预期,我这样的设计思路和glass-easel的架构思路相符吗? @LastLeaf @Tidyzq 有时间还请指导一下
- 目前看来也是将backend的每一个动作都要进行通信吗?只不过是每一个动作并不对应一个document的原生动作?
- view_controller的作用不是特别明白,可以展开讲讲不?
- 这个backend的实现有验证过性能吗?我简单的试了下,如果是循环生成300个列表(列表为文本),在service即backend动作发送消息的时间间隔就已经达到3-4百毫秒级了(这里只统计一次渲染的消息发送间隔),这样的话性能不会受影响吗?当然也有可能是我用法不对,可以帮忙提供一个简单的使用示例吗?
@fanyipin
在点击的时候响应很慢,这个消耗貌似不在通信,我看service从接收到点击事件到最后一次的时间间隔都有将近2s的时间,具体可见下图:
github-backend.bak.mp4 上图中右侧为service即glass-easel backend运行的webview,在点击时,我会先清空数组,然后再赋值300项,并动态渲染元素,可以看到在backend侧间隔时间就要接近2s,所以是不是我的用法有问题?
从视频上看你没有做通讯合并操作,耗时花费在了单次操作的通讯消耗。你可以尝试每个微任务合并通讯。
// 参考 https://github.com/Tidyzq/glass-easel/blob/wip-shadow-sync/glass-easel-shadow-sync/tests/base/env.ts。
const createBridge = () => {
let _cb: ((...args: any[]) => void) | null = null
const subscribe = (cb: (...args: any[]) => void) => {
_cb = cb
}
let queue = []
const publish = (args: readonly any[]): void => {
// 每个微任务合并
queue.push(args)
if (queue.length === 1) {
Promise.resolve().then(() => {
queue.forEach(args => _cb?.(args))
queue = []
})
}
}
return { subscribe, publish }
}
const bridgeToView = createBridge()
const bridgeToData = createBridge()
const messageChannelDataSide = MessageChannelDataSide(
bridgeToView.publish,
bridgeToData.subscribe,
getLinearIdGenerator,
)
const messageChannelViewSide = MessageChannelViewSide(
bridgeToData.publish,
bridgeToView.subscribe,
syncController,
getLinearIdGenerator,
)
- 目前看来也是将backend的每一个动作都要进行通信吗?只不过是每一个动作并不对应一个document的原生动作?
- view_controller的作用不是特别明白,可以展开讲讲不?
- 这个backend的实现有验证过性能吗?我简单的试了下,如果是循环生成300个列表(列表为文本),在service即backend动作发送消息的时间间隔就已经达到3-4百毫秒级了(这里只统计一次渲染的消息发送间隔),这样的话性能不会受影响吗?当然也有可能是我用法不对,可以帮忙提供一个简单的使用示例吗?
@fanyipin
- 通信应该要做合并操作,而不是每个动作都进行一次通讯。简单的你可以每个微任务合并成一个,复杂的应该是根据 setData 以及事件触发做合并。
backend.ts 用于逻辑侧,view_controller.ts 用于渲染侧。这是一个还未完成的模块所以没有完善的指引,大致使用方式是:
- 在逻辑侧使用 backend.ts 提供的 ShadowDomBackendContext 作为自定义渲染后端。
- 在逻辑侧使用 MessageChannelDataSide 将后端操作转换为指令流,传入具体的通讯实现。(你应该在这里做合并)
- 渲染侧需要准备好 glass_easel 环境,一个渲染后端,以及一个挂载点。
- 在渲染侧使用 view_controller.ts 提供的 MessageChannelViewSide 接收指令流,传入具体的通讯实现。
- 在渲染侧使用 ViewController 将指令流转换为挂载点上的树操作。 简单用法可以参考模块内的单元测试代码 https://github.com/Tidyzq/glass-easel/blob/wip-shadow-sync/glass-easel-shadow-sync/tests/base/env.ts。
- 用单元测试代码即可验证,验证过没有你说的这么大开销。
在点击的时候响应很慢,这个消耗貌似不在通信,我看service从接收到点击事件到最后一次的时间间隔都有将近2s的时间,具体可见下图: github-backend.bak.mp4 上图中右侧为service即glass-easel backend运行的webview,在点击时,我会先清空数组,然后再赋值300项,并动态渲染元素,可以看到在backend侧间隔时间就要接近2s,所以是不是我的用法有问题?
从视频上看你没有做通讯合并操作,耗时花费在了单次操作的通讯消耗。你可以尝试每个微任务合并通讯。
// 参考 https://github.com/Tidyzq/glass-easel/blob/wip-shadow-sync/glass-easel-shadow-sync/tests/base/env.ts。 const createBridge = () => { let _cb: ((...args: any[]) => void) | null = null const subscribe = (cb: (...args: any[]) => void) => { _cb = cb } let queue = [] const publish = (args: readonly any[]): void => { // 每个微任务合并 queue.push(args) if (queue.length === 1) { Promise.resolve().then(() => { queue.forEach(args => _cb?.(args)) queue = [] }) } } return { subscribe, publish } } const bridgeToView = createBridge() const bridgeToData = createBridge() const messageChannelDataSide = MessageChannelDataSide( bridgeToView.publish, bridgeToData.subscribe, getLinearIdGenerator, ) const messageChannelViewSide = MessageChannelViewSide( bridgeToData.publish, bridgeToView.subscribe, syncController, getLinearIdGenerator, )
嗯,我再尝试下,这个应该也跟我开着devtool + console有关。不过有一点还是不太了解,用户点击事件到service接收响应再到setData目前不都是框架本身处理的吗?我怎么根据setData去做合并呢?我现在了解到的还是在自定义的context中的相关api里实现自定义逻辑
我想做的事情大概是这样:在electron中自定义backend,模式为shadowroot,其中两个webview,service webview运行glass-easel,view webview通过接收service webview的消息进行对dom节点进行绘制,两个webview间通过ipc通信。 在实施过程中,发现如果有1000个循环节点的发,要发送14000多次消息,渲染延迟1-2s。我理解是不是某些service的动作不需要通信消耗,这块有什么好建议吗?感觉现在通信消耗大大的延缓了界面的绘制与响应。 或者这个方向可行吗