API_FrameTick

DigitalTwinAPI之tick功能使用说明

2023.03.17

一、功能简介

tick功能提供了一种高性能的DigitalTwinAPI接口调用方式。

常规的DigitalTwinAPI调用流程是这样的:客户端调用DigitalTwinAPI(通过网络传输),后台渲染进程接收命令,然后进行异步处理。这样就有几个方面的延迟:

(1)客户端的异步等待(命令发送后等待返回结果)

(2)网络传输(通过WebRTC)

(3)后台渲染进程的线程切换(接收线程收到数据后,投递到执行线程,执行线程处理后投递到发送线程)

通过上面的流程可以看到如果高频率的调用接口,性能是不会太高的。为了解决这一问题,实现了tick功能。通过tick功能,可以在渲染进程的每帧直接同步地调用DigitalTwinAPI,极大的提高了调用性能。

二、实现原理

tick功能是通过渲染进程内嵌的chrome浏览器内核(cef)执行JS脚本达到目的的。

调用注册接口后,进程会创建一个cef浏览器引擎,参数url将被设置为浏览器要显示的页面,在此页面内的DigitalTwinAPI调用,都是在同进程直接调用底层C++接口的,C++处理完成后,通过回调cef页面的Javascript函数来通知JS进行后续操作。 由于是在同一个进程内进行JS/C++互操作,所以性能非常高。

三、使用详解

1、接口说明

总共有4个方法:

registerTick

image-20230105141018099

registerTick用于注册tick功能,注意:全局只能注册一个tick页面,多次registerTick,后面的tick功能会覆盖掉前面的。参数说明:

  • url

    tick功能是通过一个独立的html页面实现的,此参数用来设置页面的地址。注意:由于是在后台渲染进程执行,所以页面地址必须是完整的网络路径或者服务器上的本地绝对路径, 不能传递相对路径。

  • options

    用来设置是否显示调试页面,由于cef调试JS比较麻烦,所以可以设置此参数,将tick页面显示出来,将调试信息在页面上显示,这样就极大的方便了代码调试。options支持以下属性(可设置一个或多个属性,未设置的属性会使用默认值):

    • x

      调试窗口距离左上角的X偏移量。 默认值4

    • y

      调试窗口距离左上角的Y偏移量。默认值4

    • width

      调试窗口的宽度。默认值400

    • height

      调试窗口的高度。默认值300

    • visible

      是否可见。 默认为false(不可见)

removeTck

image-20230317181607778

removeTick用于移除tick功能,调用此方法后,tick功能会停止。

showTickWindow

image-20230317182007372

showTickWindow用于显示/隐藏调试窗口。此方法既可以在客户端调用,也可以在tick页面调用。

executeJsInTickPage

image-20230317182143177

executeJsInTickPage用于实现客户端页面与tick页面的通信,通过此方法,可以在客户端页面执行tick页面的JS代码。

客户端页面和tick页面(服务器页面)之间可以双向通信,在tick页面调用ue.internal.postevent("str"),可以实现tick页面向客户端页面发送消息

2、tick页面说明

在tick页面里调用DigitalTwinAPI与常规调用有一些区别,主要体现在以下几个方面:

(1)DigitalTwinAPI初始化
var fdapi; 
window.onload = function () {
        fdapi = new DigitalTwinAPI();
}

一般在onload里初始化,与常规初始化不同,tick页面里初始化DigitalTwinAPI不需要传递任何参数。注意:这里也可以使用异步操作例如:

window.onload = async function () {
        fdapi = new DigitalTwinAPI();        
        await fdapi.tag.delete(id);
        await fdapi.tag.add(data);
}
(2)两个固定方法
function tick(frameNo) {
    let data = fdapi.tag.setText(id, 'Tag:' + (i++).toString(), null);
    document.getElementById('r1').innerText = `[Frame:${frameNo}] ` + JSON.stringify(data);
}

function tick_next(o, frameNo) {
    if (o.command == CommandType.Tag_Update) {
        fdapi.tag.get(id, null);
    }
    else if (o.command == CommandType.Tag_Get) {
        document.getElementById('r2').innerText = `[Frame:${frameNo}] ` + JSON.stringify(o);
    }
}

tick页面里有2个被底层调用的固定方法:tick和tick_next,这2个方法构成一个闭环。

  • tick

    参数frameNo,是当前帧号

    是主页面调用registerTick后,渲染进程在每帧都会回调的方法。

  • tick_next

    可选的方法
    参数o:是调用的DigitalTwinAPI接口的执行结果,通过此o.command可以知道tick_next是哪个接口执行完后返回的

    参数frameNo,是当前帧号

如果在tick里调用了DigitalTwinAPI的接口,渲染进程执行完后,由tick_next通知。 如果不需要接口的返回值,可以不实现此方法

在这tick和tick_next里调用DigitalTwinAPI有以下几个注意事项:

  • 在tick和tick_next方法里调用接口,要想获取接口返回值,接口的最后一个参数fn必须设置为null,例如下面的代码:
    fdapi.tag.setText(id, 'Tag:' + (i++).toString(), null);
    用于每帧设置标签文本,同时希望收到底层设置完以后的通知,那么就把最后一个参数设置为null,底层设置完后会调用tick_next方法,tick_next的参数就是详细的执行结果
  • 如果把最后一个参数设置为null,那么该API调用后的方法返回值是JS层包装的将要传递到底层的JSON命令对象,具体请看下面实例运行结果
  • 如果不关心底层处理结果,可以不用设置最后一个参数为null,例如:
    fdapi.tag.setText(id, 'Tag:' + (i++).toString());
    此方法没有将最后一个参数设置为null,那么底层执行完后,将不会调用tick_next
  • 如果将最后一个参数设置为null,那么tick和tick_next和底层的任务执行都是在同一个线程,也就是同步调用,所以性能很高。 但是如果没有设置最后一个参数为null,那就跟常规调用一样,是异步的,这时如果使用异步调用的3种方式去等待执行结果,会严重影响性能,失去了tick的意义!

如下代码:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>TICK</title>
    <script type="text/javascript" src="../../ac_conf.js"></script>
    <script type="text/javascript" src="../../ac.min.js"></script>
    <style type="text/css">
        body {
            font-family: Verdana, Geneva, Tahoma, sans-serif;
            font-size: 9pt;
            background-color: rgb(199, 237, 204);
            overflow: scroll;
        }
    </style>
</head>
<body>
    <strong>Test Debug Window</strong> 
     <a href="javascript:fdapi.tag.delete(id)">DeleteTag</a>
     <a href="javascript:ue.internal.postevent('test string')">PostEvent</a>
     <a href="javascript:fdapi.showTickWindow(false);">Close</a>
    <hr>
    <span id="r1"></span>
    <hr>
    <span id="r2"></span>
    <hr>
    <span id="r3"></span>
</body>
</html>
<script>
    var fdapi;
    var id = 'testTag';
    var i = 0;

    var data = {
        id: id,
        coordinate: [493217, 2491901, -1.9],
        text: 'Tag:0',
        textSize: 20,
        textColor: Color.Yellow,
        range: [1, 10000],
        showLine: false
    }


    window.onload = function () {
        fdapi = new DigitalTwinAPI();
        fdapi.tag.delete(id);
        fdapi.tag.add(data, o => {
            document.getElementById('r3').innerText = 'tag created.'
            fdapi.tag.focus(id, 100, 0);
        });
    }

    function clientCalled(str) {
        document.getElementById('r3').innerText = str;
    }

    function tick(frameNo) {
        let data = fdapi.tag.setText(id, 'Tag:' + (i++).toString(), null);
        document.getElementById('r1').innerText = `[Frame:${frameNo}] ` + JSON.stringify(data);
    }

    function tick_next(o, frameNo) {
        if (o.command == CommandType.Tag_Update) {
            fdapi.tag.get(id, null);
        }
        else if (o.command == CommandType.Tag_Get) {
            document.getElementById('r2').innerText = `[Frame:${frameNo}] ` + JSON.stringify(o);
        }
    }
</script>

上面代码实现的功能是:每帧改变标签的值,然后获取该标签的信息,在tick页面上显示出来。代码运行效果:

image-20230105145742302