我有一个想法:用 Rust 来撸 UI

简介: RN 撸 UI 很爽,不仅跨平台,而且可动态化,可是性能就真的一般般。 Flutter 性能还行,可是强加上动态化的方案,性能也就那样了。造轮子永无止境,在 SwiftUI 和 Compose 先后问世后,我也在思考如何利用新的技术来优化 RN 的性能或者创造出全新的框架,有一些想法,也做了些尝试。

缘起


RN 撸 UI 很爽,不仅跨平台,而且可动态化,可是性能就真的一般般。 Flutter 性能还行,可是强加上动态化的方案,性能也就那样了。造轮子永无止境,在 SwiftUI 和 Compose 先后问世后,我也在思考如何利用新的技术来优化 RN 的性能或者创造出全新的框架,有一些想法,也做了些尝试。


最开始的方案想法是:既然 native 端已经有新的架构实现了,那么将 RN native 端实现用新的架构实现下,是否性能就能有提升,其依据是 React、Compose 都是函数式的,实现也比较类似。简单尝试了下,发现自己少考虑了 React 的 Virtual Dom,如果用 Compose 来实现 RN 的 native 端,这玩意儿就是个鸡肋,实现就变成了函数式到 Virtual Dom 的树结构,再到 Compose 的函数式,Compose 的底层也会有 diff 的。所以就是绕了几个大圈。


然后就有了新的想法:不要用 React 了,而只是用它的函数式语法,这语法与 Compose 的语法很类似,那我能不能直接翻译过去呢?因为要动态化,我想到了 babel,借助 babel 将它转换成 ast 结构,然后在 Compose 环境下解释执行这个 ast。思路有了,那就开干了,并且把 demo 给搞出来了, 有兴趣的可以去我的 github 上看看。地址:github.com/cgspine/Rec…


想法是美好的,直到 leader 问了我个问题:你如何证明你的解释器是图灵完备的?

6c33f5d9a4621b26fbccf81addbc4c3.png

这就超过我的知识盲区了。随后 leader 就指导了下我:要把语言逻辑层和 UI 渲染拆分开,语言逻辑层往 wasm 方向想想看。


那具体要怎么把 wasm 和 compose 结合起来呢?其实关键是前端语言和 compose 间的接口怎么实现,怎么才能把 UI 渲染层抽象出来。


虽然当时没什么思路,但我的框架思维风暴能力还是不错的,后面想通了怎么设计这些个接口。所以又开始了我的 demo 之路。 我选用了 Rust 语言来写前端,然后将 Rust 代码编译成 wasm,然后由 android 端解释执行这份 wasm 代码。 为什么要选用 Rust 呢?一个是因为它编译成 wasm 的文档写得比较好,第二个是我它的一个语法糖对我的接口实现很有帮助。

API 设计:


首先我们来看下 Demo 的使用:


Rust 端:

use wasm_bindgen::prelude::*;
use composable::*;
use composable_derive::Composable;
#[derive(Composable)]
struct Column();
#[derive(Composable)]
struct Text;
pub fn noop(){
}
pub fn list(){
    for i in 0..10{
        Text::compose("{\"text\": \"UI from Rust\",\"size\": 17,\"padding\": 20}".into(), noop);
    }
}
#[wasm_bindgen]
pub fn start() {
    Column::compose("{\"background\": \"white\"}".into(), list);
}

入口函数为 start,它声明了一个 attribute,名字叫 wasm_bindgen,顾名思义,它就是在编译成 wasm 后为 native 提供的钩子函数。


Column、Text 这些 struct 声明了 #[derive(Composable)] 这个 attribute,这个是我定义的,其作用是桥接 native 的 Compose 组件,所有的黑科技都在 #[derive(Composable)] 里了。其作用是为 struct 用代码生成的方式实现 Composable 这个 trait(也就是 java 里的接口)。


Composable 的定义为:

pub trait Composable {
    fn compose(props: &str, children: fn());
}

在了解了这几个接口后, 我们就可以从 start 函数推断出整个 Demo 是长什么样的了:就是一个 Column,背景为白色,然后里面包含了 10 个 Text 组件,文案为 “UI From Rust”, 字体大小 17,并且有 20dp 的 padding。


Android 端是怎么使用呢?

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            WasmPage(ins = this.assets.open("recos.wat"))
        }
    }
}

我封装了 WasmPage 组件,然后用它传递 wasm 文件的路径就可以跑起来了,是不是感觉很完美?


最终跑起来的效果图:

64a7bc08ddb3c6c9b5e3893251148cd.png

实现原理


很多框架,很是高大上,但其实其核心原理却是非常简单的,例如 Vue,其最初的版本核心不过是 利用了 Object.definePropertity 方法。所以最为重要的是我们能不能发现并利用接口?能不能设计出对用户更友好的接口?有没有毅力完善周边功能并不断迭代下去?

我的实现原理其实也是很简单的,就是个面向切面编程的应用:


函数式编程,其实际上就是函数调用函数,再往底层走,就是一个栈的操作。


那我们现在来看看 start 函数的栈调用(由于还不知道 Composeable::compose(props: &str, children: fn()) 的具体实现,我们先假设其是直接调用 childen 方法):

5003997e24604da0980e19a1fc84a6d.png

如果我们在 Composeable::compose 方法调用 children 前后通知下 native, 然后 native 也同样构建个栈结构来记录下相应的调用, 那么 native 端就可以获取整个 UI 结构的声明。


我们通过对 Composeable:: compose 的切面代理,就可以拿到我们关心的 UI 结构,而其它的逻辑部分就全由 Rust 搞定,原理还是挺简单的了。

代码实现


原理想清楚了,代码写起来就简单了。


Rust 端主要的就是 Composeable 的实现, library 实现为:

use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = recos, js_name = nativeComposeBefore)]
    pub fn nativeComposeBefore(name: &str, s: &str) -> i32;
    #[wasm_bindgen(js_namespace = recos, js_name = nativeComposeAfter)]
    pub fn nativeComposeAfter(obj: i32);
    #[wasm_bindgen(js_namespace = recos, js_name = log)]
    pub fn log(tag: &str, content: &str);
}
pub trait Composable {
    fn compose(props: &str, children: fn());
}

这里主要是定义了几个 native 端接口函数, 然后 derive (代码生成) 的代码为:

#[proc_macro_derive(Composable)]
pub fn composable(input: TokenStream) -> TokenStream {
    let ast:DeriveInput = syn::parse(input).unwrap();
    let name = &ast.ident;
    let gen = quote! {
        impl Composable for #name {
          fn compose(props:&str, children: fn()){
            // 调用 children 前,先通知给 native 端,并把属性信传回去
            let obj = nativeComposeBefore(stringify!(#name).into(), props);
            children();
            // 调用 children 后,再通知 native 端完成构建
            nativeComposeAfter(obj.clone());
          }
        }
    };
    return gen.into();
}

Android Compose 端实现为:

@Composable
fun WasmPage(ins: InputStream) {
    val element = remember { mutableStateOf<WasmElement?>(null) }
    LaunchedEffect(ins) {
        // 子线程执行解析
        var program: KWasmProgram? = null
        var rootWasmElement: WasmElement? = null
        val stack = Stack<ElementBuilding>()
        val programBuilder = withContext(Dispatchers.IO) {
            val builder = KWasmProgram.builder(ByteBufferMemoryProvider(16 * 1024 * 1024))
                .withTextFormatModule(
                    "d",
                    ins
                )
                   // 注册 nativeComposeBefore 方法                   
                  .withHostFunction(
                    namespace = "wbg",
                    name = "__wbg_nativeComposeBefore",
                    hostFunction = HostFunction { offset1: IntValue, len1: IntValue, offset2: IntValue, len2: IntValue, context ->
                        val name = ByteArray(len1.value)
                        context.memory?.readBytes(name, offset1.value)
                        val props = ByteArray(len2.value)
                        context.memory?.readBytes(props, offset2.value)
                        // 不同的 struct 解析成不同的 element.
                        val el = when (name.toString(Charsets.UTF_8)) {
                            "Column" -> ColumnElement(props.toString(Charsets.UTF_8))
                            "Text" -> TextElement(props.toString(Charsets.UTF_8))
                            else -> WasmElement(program!!)
                        }
                        // 入栈
                        stack.push(ElementBuilding(el))
                        IntValue(el.id)
                    }
                )
                // 注册 nativeComposeAfter 方法 
                .withHostFunction(
                    namespace = "wbg",
                    name = "__wbg_nativeComposeAfter",
                    hostFunction = HostFunction { value: IntValue, context ->
                        val o = value.value
                        val item = stack.pop()
                        if(o != item.element.id){
                            throw RuntimeException("not matched.")
                        }
                        item.element.children = item.building
                        // 出栈,并添加到父元素的 children 中。
                        stack.peek()?.let {
                            it.building.add(item.element)
                        }
                        EmptyValue
                    }
                )
            builder
        }
        program = programBuilder.build()
        rootWasmElement = WasmElement(program)
        stack.push(ElementBuilding(rootWasmElement))
        program.getFunction("d", "start").invoke()
        stack.pop().let {
            it.element.children = it.building
        }
        // rust 代码跑完,那整个 UI 结构就已经完成了,可以通知 Compose 渲染了。
        element.value = rootWasmElement
    }
    // compose 渲染
    Box(modifier = Modifier.fillMaxSize().background(Color.White)){
        element.value?.Render()
    }
}

如此整体流程就跑起来了。当然,这只是一个最简单的静态场景。如果要完善它,还有很多事情要做:


1.useState、useCallback、useEffect 这些怎么实现?

2.onScoll、onLayout 等这些怎么监听?

3.Touch 事件怎么处理?

4.现在我用的是 kWASM 这个库来做wasm 解析,它的性能也不怎样,而且有很多 bug,这里基于一些成熟的 wasm 库进行包装。


核心原理挺简单的,但是要想建造成一座大厦,那还是有非常非常多的砖要搬,也有很多很多的坑要去踩。看着目前已经搬不完的业务砖, 还是 RN 香啊。

目录
相关文章
|
2月前
|
Rust 自然语言处理 API
|
5月前
|
Rust 安全 Go
揭秘Rust语言:为何它能让你在编程江湖中,既安全驰骋又高效超车,颠覆你的编程世界观!
【8月更文挑战第31天】Rust 是一门新兴的系统级编程语言,以其卓越的安全性、高性能和强大的并发能力著称。它通过独特的所有权和借用检查机制解决了内存安全问题,使开发者既能享受 C/C++ 的性能,又能避免常见的内存错误。Rust 支持零成本抽象,确保高级抽象不牺牲性能,同时提供模块化和并发编程支持,适用于系统应用、嵌入式设备及网络服务等多种场景。从简单的 “Hello World” 程序到复杂的系统开发,Rust 正逐渐成为现代软件开发的热门选择。
87 1
|
2月前
|
Rust 安全 Java
探索Rust语言的并发编程模型
探索Rust语言的并发编程模型
|
2月前
|
Rust 安全 区块链
探索Rust语言:系统编程的新选择
【10月更文挑战第27天】Rust语言以其安全性、性能和并发性在系统编程领域受到广泛关注。本文介绍了Rust的核心特性,如内存安全、高性能和强大的并发模型,以及开发技巧和实用工具,展示了Rust如何改变系统编程的面貌,并展望了其在WebAssembly、区块链和嵌入式系统等领域的未来应用。
|
2月前
|
Rust 安全 Java
编程语言新宠:Rust语言的特性、优势与实战入门
【10月更文挑战第27天】Rust语言以其独特的特性和优势在编程领域迅速崛起。本文介绍Rust的核心特性,如所有权系统和强大的并发处理能力,以及其性能和安全性优势。通过实战示例,如“Hello, World!”和线程编程,帮助读者快速入门Rust。
122 1
|
2月前
|
Rust 安全 编译器
编程语言新宠:Rust语言的特性、优势与实战入门
【10月更文挑战第26天】Rust语言诞生于2006年,由Mozilla公司的Graydon Hoare发起。作为一门系统编程语言,Rust专注于安全和高性能。通过所有权系统和生命周期管理,Rust在编译期就能消除内存泄漏等问题,适用于操作系统、嵌入式系统等高可靠性场景。
168 2
|
2月前
|
Rust 安全
深入理解Rust语言的所有权系统
深入理解Rust语言的所有权系统
54 0
|
2月前
|
Rust 安全 前端开发
探索Rust语言的异步编程模型
探索Rust语言的异步编程模型
|
2月前
|
Rust 安全 云计算
Rust语言入门:安全性与并发性的完美结合
【10月更文挑战第25天】Rust 是一种系统级编程语言,以其独特的安全性和并发性保障而著称。它提供了与 C 和 C++ 相当的性能,同时确保内存安全,避免了常见的安全问题。Rust 的所有权系统通过编译时检查保证内存安全,其零成本抽象设计使得抽象不会带来额外的性能开销。Rust 还提供了强大的并发编程工具,如线程、消息传递和原子操作,确保了数据竞争的编译时检测。这些特性使 Rust 成为编写高效、安全并发代码的理想选择。
48 0
|
3月前
|
Rust 安全 网络安全
在 Rust 语言中,寻找企业上网行为管理软件的突破
在数字化企业环境中,上网行为管理软件对于保障网络安全和提升工作效率至关重要。Rust 语言凭借其安全性、高性能和并发性,为开发此类软件提供了新机遇。本文通过几个 Rust 代码示例,展示了如何实现网址检查、访问频率统计及访问控制等功能,旨在探索 Rust 在企业上网行为管理中的应用潜力。
50 0