本文内容为作者在GIAC2022 全球互联架构大会上海站的共享总结

Doric是什么

Doric是一个极简、高效的跨端开发结构,运用TypeScript作为开发语言。
上层遵从MVVM规划,能够在多个终端渠道上履行,完成一份代码能够多处履行。
目前已经支撑Android、iOS、Web及Qt,可按开发者所需接入更多场景和渠道。

Doric名字的来源

Doric,渐进式跨端框架 —— 从2D到3D

上图为希腊的帕特农神庙,其历经数千年风雨侵蚀,早已断墙残垣。然而那些廊柱屹立千年,仍然巩固巍峨。这种廊柱款式叫做Doric(多立克柱式),即咱们这个项目的名称由来。
正如Doric款式的廊柱撑起了神庙的千年风雨,咱们也期望Doric项目能够作为前端页面的支柱,简洁,可靠。

Doric环境预备

运用Doric前,需求预备好下列开发环境:

  • Node.js(Node.js 版别需不低于 8.0,建议运用 Node.js 12.0 及以上版别)
  • npm(一般已随Node.js装置包装置好)

Doric运用TypeScript作为开发语言,引荐运用编辑器

  • Visual Studio Code

当必需的开发环境预备就绪后,即可运用npm装置Doric指令行东西:

npminstall-gdoric-cli

此指令行东西包括以下用法:

doriccreate[ProjectName]

新建一个Doric项目,项目名为ProjectName

doricdev

开端开发Doric页面,此时终端会输出二维码。可经过Doric Playground扫码查看页面效果。也能够在此方式下进行热加载,调试等

doricbuild

进行代码打包,此时输出最终编译好的javascript文件至bundle目录下。

doricrunandroid
doricrunios

别离编译Android和iOS项目,并装置App到手机或模拟器中

doric clean

清理编译缓存文件。此时会删去.dxx(旧版别为build)和bundle文件夹中内容

Doric Playground

为了便利开发者能够快速开发,取得所见即所得的开发体会,Doric Pub供给了在线版的Playground:p.doric.pub/play/

Doric,渐进式跨端框架 —— 从2D到3D

左边是一个在线的Monaco Editor编辑器,右侧便是一个所见即所得的交互展现区

Doric,渐进式跨端框架 —— 从2D到3D

该页面预置了一系列的Examples代码,选取或许更改了对应源码后可直接点击Run按钮便能够实时体会。

除在线的站点外,Doric Playground还有对应的App版别,可扫描下方二维码获取该App:

Doric,渐进式跨端框架 —— 从2D到3D

Android版别

Doric,渐进式跨端框架 —— 从2D到3D

iOS TestFlight版别

Doric,渐进式跨端框架 —— 从2D到3D

翻开App后有如上图有六个菜单项,点击开端开发会进入如下的页面,这儿运行着一个Doric实例,能够合作前面提到的doric-cli来进行热加载展现:

Doric,渐进式跨端框架 —— 从2D到3D

此外开发手册供给了基础组件和一些常见功用的代码模板和示例如下:

展现视频1

其中3D模型菜单点击后会进入一个3D模型展现页,是一个经典的Littlest Tokyo的GLB格局的模型。

展现视频2

Doric编程范式

声明式UI

声明式UI在当今移动互联网的布景下,已经有太多的结构都选用了这种范式。和传统的指令式UI不同。声明式UI只描述当前的UI状况,并且不需求关心它是怎么过渡的。而指令式UI则需求编程者构建全功用UI实体,然后在UI更改时运用办法对其进行改变。因此选用声明式的UI的好处也显而易见,不需求手动刷新数据,较强兼容性 ,并且能够加快开发 ,一般结构均会供给了许多开箱即用的组件,一同精简代码数量 ,减少缺陷的呈现,许多都支撑实时预览 ,能够做到真实的所见所即得。Doric自身也供给了声明式的UI编程方式,下面是一个Hello World的代码:

@Entry
export class HelloDoric extends Panel {
  onShow() {
    navbar(this.context).setTitle("Doric HelloWorld");
  }
  build(root: Group) {
    let count = 0;
    let myText: Text;
    vlayout(
      [
        image({
          imageUrl: "https://www.6hu.cc/wp-content/uploads/2023/06/1686130626-6f690a38c131263.png",
          onClick: () => {
            myText.text = `${++count}`;
          }
        }),
        myText = text({
          text: "0",
          textSize: 12,
          textColor: Color.RED,
        }),
      ],
      {
        layoutConfig: layoutConfig().fit().configAlignment(Gravity.Center),
        space: 30,
        gravity: Gravity.Center,
      }
    ).in(root);
  }
}

这儿能够看出,视图装备一般是不可变的,若一定需求改动UI结构,也能够经过API结构新的子视图树进行替换即可,遵从state改动UI状况这种方式。

TSX

JSX是Javascript和XML结合的一种格局。React发明晰JSX,运用HTML语法来创立虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。JSX 只是为React.createElement(component, props, …children) 办法供给的语法糖。React 自创了JSX语法,是一个 JavaScript 的语法扩展,JSX 能够更好的描述 UI 应该呈现出它应有交互的本质方式。在Doric编程中TSX也是支撑的,能够参阅如下代码示例:

@Entry
export class HelloDoric extends Panel {
  onShow() {
    navbar(this.context).setTitle("Doric HelloWorld");
  }
  build(root: Group) {
    let count = 0;
    let myTextRef = createRef<Text>();
    <VLayout
      parent={root}
      gravity={Gravity.Center}
      space={30}
      layoutConfig={layoutConfig().fit().configAlignment(Gravity.Center)}
    >
      <Image
        imageUrl="https://www.6hu.cc/wp-content/uploads/2023/06/1686130626-6f690a38c131263.png"
        onClick={() => {
          myTextRef.current.text = `${++count}`;
        }}
      ></Image>
      <Text ref={myTextRef} text="0" textSize={12} textColor={Color.RED}></Text>
    </VLayout>;
  }
}

经过TSX这种编程方式呢,也能够后期更好结合适配低代码(Low Code)渠道

MVVM

Model-View-ViewModel(MVVM)已成为业界数据绑定与Model展现层相结合是非常好的做法,这种方式使得开发人员能够将View和逻辑分离出来,数据绑定技术也非常简略实用。在Doric中也预置了这种编码风格:

首要是Model,View,ViewModel注册联系层:

@Entry
export class CounterPage extends VMPanel<CountModel, CounterView> {
  state = {
    count: 1,
  }
  constructor() {
    super();
    log("Constructor");
  }
  getViewHolderClass() {
    return CounterView;
  }
  getViewModelClass() {
    return CounterVM;
  }
  getState(): CountModel {
    return this.state;
  }
}

接着是Model部分的代码:

interface CountModel {
  count: number;
}

View部分的代码:

class CounterView extends ViewHolder {
  number!: Text;
  counter!: Text;
  build(root: Group) {
    let group = vlayout(
      [
        this.number = text({
          textSize: 40,
          tag: "tvNumber",
        }),
        this.counter = text({
          text: "Click To Count 1",
          textSize: 20,
          tag: "tvCounter",
        }),
      ],
      {
        layoutConfig: layoutConfig().most(),
        gravity: Gravity.Center,
        space: 20,
      }
    ).in(root);
  }
}

最终是ViewMode部分的数据视图联系的代码:

class CounterVM extends ViewModel<CountModel, CounterView> {
  onAttached(s: CountModel, vh: CounterView) {
    vh.counter.onClick = () => {
      this.updateState(state => {
        state.count++
      })
    };
  }
  onBind(s: CountModel, vh: CounterView) {
    vh.number.text = `${s.count}`;
  }
}

MobX

引证一段官方文档关于MobX此结构的描述和MobX的全体运作机制图:作为一个经过战火洗礼的库,它经过通明的函数呼应式编程(transparently applying functional reactive programming – TFRP)使得状况办理变得简略和可扩展。MobX背面的哲学很简略:任何源自运用状况的东西都应该自动地取得。

Doric,渐进式跨端框架 —— 从2D到3D

在Doric中咱们也能够借助MobX来办理咱们的UI状况可见如下的ToDo列表的代码示例:

首要咱们定义要运用的数据原型:

interface ToDoItem {
  title: string;
  checked: boolean;
  deadline: number;
}

接着经过Decorator标示需求观测的特色和计算值:

class ToDoList {
  constructor() {
    makeObservable(this);
  }
  @observable todos: ToDoItem[] = [];
  @computed get allLength() {
    return this.todos.length;
  }
  @action.bound
  add(todo: ToDoItem) {
    this.todos.push(todo);
  }
}

经过observe相关的数据引发部分UI改变:

export class ToDoListPanel extends Panel {
  todos = new ToDoList();
  build(root: Group) {
    <Stack layoutConfig={layoutConfig().most()} parent={root}>
      {observer(() => (
        <List
          layoutConfig={layoutConfig().most()}
          itemCount={this.todos.allLength}
          renderItem={(idx) =>
            (<ToDoCell item={this.todos.todos[idx]} />) as ListItem
          }
        />
      ))}
      <AddButton
        onClick={async () => {
          const text = await modal(this.context).prompt({
            title: "Please input todo",
          });
          this.todos.add({
            title: text,
            checked: false,
            deadline: 0,
          });
        }}
      />
    </Stack>;
  }
}

Doric全体架构和特色

首要Doric全体API规划的比较贴合原生,关于原生开发者较为友爱,上手也会较快。此外Doric自身烘托功能较高,并有完善的周边开发东西箱(Devkit),且选用JS Bundle发布,支撑动态化。

Doric,渐进式跨端框架 —— 从2D到3D

上图为Doric全体的结构图,从图中能够看到,Doric SDK烘托层面抹平了多个渠道差异,供给插件能力支撑多个扩展能力如网络,路由,组件和APM统计从而支撑起不同的事务。周边的东西链,发布系统,监控服务也能够更好的支撑事务快速接入运用

Doric烘托机制

JS Tree

在Doric JS SDK履行完如下一段声明式UI的代码后,会生成一个相应的JS Tree树结构

vlayout(
  [
    image({
      imageUrl: "https://www.6hu.cc/wp-content/uploads/2023/06/1686130626-6f690a38c131263.png",
      onClick: () => {
        myText.text = `${++count}`;
      }
    }),
    myText = text({
      text: "0",
      textSize: 12,
      textColor: Color.RED,
    }),
  ],
  {
    layoutConfig: layoutConfig().fit().configAlignment(Gravity.Center),
    space: 30,
    gravity: Gravity.Center,
  }
).in(root);

JS Tree树结构:

Doric,渐进式跨端框架 —— 从2D到3D

全量烘托和增量烘托

上述JS Tree会被转化为如下的JSON结构,咱们称之为Dirty props(脏特色),这部分结构会被传到原生烘托侧作全量的烘托

Doric,渐进式跨端框架 —— 从2D到3D

而当image呼应了点击后,Doric会找到需求改变的视图:

Doric,渐进式跨端框架 —— 从2D到3D

从而取得一个增量待改变的Dirty props:

Doric,渐进式跨端框架 —— 从2D到3D

这样的好处是能够很大程度减小从JS到Native侧的数据传输,一同控制也更为精确。

因为有了JS Tree结构这层,故而Doric完成SSR(Server Side Rendering)服务端烘托会较为容易,当一个Doric组件未选用SSR优化方案时,其履行流程如下图所示:

Doric,渐进式跨端框架 —— 从2D到3D

Doric经过履行对应组件打包好的Bundle JS生成视图DOM数据从而全量烘托首屏UI,后续则在此基础上做随数据改变的部分更新。

而当选用了首屏SSR优化后,则开端时Doric便能够直接运用SSR做好的视图DOM数据做全量烘托,这关于较为复杂的页面会有很大程度的优化:

Doric,渐进式跨端框架 —— 从2D到3D

归纳如上的Doric烘托机制咱们做了如下的功能benchmark,以100个视图平铺为例,别离对比了纯原生,Doric,React Native以及Flutter,在全量和增量烘托耗时,所占用的内存大小以及CPU占用率等4个维度上做了对比,可见Doric是存在一定的功能优势的:

Doric,渐进式跨端框架 —— 从2D到3D

Devkit东西链

为了便利Doric开发者,Doric供给了完整的开发东西箱(Devkit),此Devkit包括了热加载(Hot Reload)所见即所得,Doric实例的源码查看,实时功能Profiler,以及随时可查看Doric实例内的视图树结构。

Doric,渐进式跨端框架 —— 从2D到3D

Devkit东西箱菜单

Doric,渐进式跨端框架 —— 从2D到3D

Doric Profiler

Doric,渐进式跨端框架 —— 从2D到3D

Doric视图树查看器

此外,经过Doric CLI创立的工程自身即可借助Visual Studio Code来进行断点调试,并不需求装置任何VS Code插件,如下所示:

Doric,渐进式跨端框架 —— 从2D到3D

2D拓宽到3D

Doric原生组件侧支撑了相似CSS 2D和3D的动画,这样能够使得原生视图组件能够以动画的形状完成二维或许三维的旋转改换等:

展现视频

但在此基础上咱们期望能够进一步支撑实时三维制作,因此,Doric内核侧完成了WebGL的规范协议和一套高效的二进制数据加载和传输机制,其中在WebGL规范完成层做了如下四方面:WebGL API到OpenGL ES API映射,GL层目标和JS侧目标联系办理,GL指令流办理以及AnimationFrame办理。此外还完成了一部分试验性的工作能够将部分GL层的API经过Google ANGLE胶水层将其运行在iOS Metal或许Android的Vulkan之上。

Doric,渐进式跨端框架 —— 从2D到3D

经过对齐WebGL规范层,一些支撑Headless的WebGL的引擎如three.js,babylon.js和PLAYCANVAS等就能够运行在这套规范之上。

WebGL API到OpenGL ES API映射主要是分两部分,一部分是GL的宏常量数值映射,另一方面就是GL API映射,详细能够参阅如下链接:

github.com/doric-pub/D…

而GL目标层的办理主要是办理如下几类目标:

  • WebGLBuffer
  • WebGLFramebuffer
  • WebGLRenderbuffer
  • WebGLTexture
  • WebGLQuery
  • WebGLSampler
  • WebGLTransformFeedback
  • WebGLVertexArrayObject

而关于GL层面的指令流处理,Doric会把这些GL指令逐一转化为

std::function<void(void)>

这样的vector结构,当显式调用flush或许WebGL函数需求立即返回时进行批量履行清空寄存GL指令的此vector,详细流程可见下图:

Doric,渐进式跨端框架 —— 从2D到3D

此外关于AnimationFrame的办理,Doric也做到了多个GL上下文进行阻隔,经过监控Android Choreographer和iOS CADisplayLink做到VSync同步,一同任意上下文的GL线程倘若过载会传导到其他上下文的GL线程,以避免呈现冻屏等问题。

Doric,渐进式跨端框架 —— 从2D到3D

高效的二进制数据传输

Doric完成了一套自办理的资源系统,在这套系统下,二进制数据(ArrayBufferLike)能够很高效的传输和操作,从而给Doric生态带来了更多的进阶玩法。如下面这个实时图像处理的demo:

展现视频

能够很便捷的对图片进行通明度,二值化以及模糊处理。

此外关于加载3D模型(包括动画)进入场景中也变得较为容易:

展现视频

展现视频

展现视频

展现视频

紧缩纹路

Doric也完成了针关于紧缩纹路烘托的接口如glCompressedTexImage2D和glCompressedTexSubImage2D,从而能够烘托出dds和ktx等格局的紧缩纹路:

展现视频

DDS格局的紧缩纹路

展现视频

KTX格局的紧缩纹路

Doric也适用于一些小型互动类3D游戏,3D模型加载与互动以及需求播放骨骼动画等事务场景

Doric,渐进式跨端框架 —— 从2D到3D

3D国际象棋,包括完整的象棋逻辑,包括每种棋子的GLTF模型,源码合计1589行,可参见链接github.com/doric-pub/D…

Doric,渐进式跨端框架 —— 从2D到3D

比较经典的一个GLTF模型案例,以HDR作为环境光,模型表面纹路漫反射等,可参见链接github.com/doric-pub/D…

Doric,渐进式跨端框架 —— 从2D到3D

无需额定模型资源,纯源码完成几何体和阴影,物理碰撞检测等,源码合计1079行,可参见链接github.com/doric-pub/D…

Doric,渐进式跨端框架 —— 从2D到3D

一个流光溢彩的数字藏馆,也是以HDR作为环境光,包括shader后置处理等

Doric,渐进式跨端框架 —— 从2D到3D

纯WebGL完成Spine动画资源加载和播放,支撑JSON和Binary两种格局,支撑一些特殊效果如拉伸抖动等,支撑动画切换和换肤以及能够经过Debug骨骼脉络展现,可参阅链接github.com/doric-pub/D…

最终,欢迎读者加入Doric社区来一同共建Doric生态:

Doric,渐进式跨端框架 —— 从2D到3D


Doric,渐进式跨端框架 —— 从2D到3D

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。