午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

使用Rust構(gòu)建一個快速的Electron APP

 flyk0tcfb46p9f 2018-03-24

前言

當(dāng)我研發(fā)Finda時,我非常希望它能夠做到快速,最好能在16毫秒內(nèi)相應(yīng)所有用戶輸入。

經(jīng)過認(rèn)真研究,我們驚訝地發(fā)現(xiàn)Finda是用Electron構(gòu)建的,該框架經(jīng)常被批評速度緩慢。

在本文中,我將重點(diǎn)說明如何在充分利用Electron易于打包、可以訪問復(fù)雜操作系統(tǒng)指定API、針對瀏覽器的視覺功能等優(yōu)點(diǎn)的同時,借助Rust來最大限度地減少不可預(yù)知的延遲現(xiàn)象和解決內(nèi)存使用過多問題。

關(guān)于設(shè)計(jì)的注意事項(xiàng)

在深入了解技術(shù)細(xì)節(jié)之前,我們首先要了解Finda自身的設(shè)計(jì)目標(biāo)。

Finda支持單一交互:用戶輸入內(nèi)容,它就能找到相應(yīng)的事物,包括瀏覽器標(biāo)簽、文字編輯器緩沖區(qū)、本地文件、瀏覽器歷史記錄、打開的窗口等。

演示視頻請參考:https://d189ym6tlc5mr2./video/2018_02_16_finda_demo.mp4。

我們最后的目標(biāo)是,想要讓Finda感覺不像是應(yīng)用程序,更像是Command-Tab(macOS默認(rèn)應(yīng)用程序切換工具),只作為操作系統(tǒng)的一部分,在需要時立即出現(xiàn),在完成相應(yīng)功能后就可以消失。

過程中無需菜單、窗口、按鈕或任何類型的本地用戶界面。針對于Finda的互動,我們只需要以下幾點(diǎn):

1、不管在哪個應(yīng)用程序的界面上,全局快捷方式都可以直接將Finda全屏顯示;

2、捕獲輸入的按鍵;

3、呈現(xiàn)搜索結(jié)果。

在不使用的情況下,F(xiàn)inda應(yīng)該隱藏在后臺。

不使用Electron的替代方案

鑒于上述要求,我重新考慮了我的選項(xiàng)。

Native OS X:我很早就想到了這一方案,其原因有兩個:

1、我想要將Finda移植到Windows和Linux上,因?yàn)閎eta測試者在問他們是否可以為他們現(xiàn)有平臺購買一個版本。

2、為了使用XCode進(jìn)行本地開發(fā),我必須升級macOS,這一升級過程幾乎肯定會在一定程度上破壞我電腦的環(huán)境。

Game-like:我之前曾經(jīng)基于此方案寫過一個像素著色器,經(jīng)過實(shí)際使用,游戲的速度非???,也許這一方案能夠有效。經(jīng)過研究,我決定嘗試使用ggez(https://github.com/ggez/ggez),這是一個基于SDL的Rust游戲庫,非常棒。

對于我這樣圖形方面的新手來說,我發(fā)現(xiàn)這個API是非常友好的。然而我很快就意識到,恐怕要制作一個完整的應(yīng)用程序,還是需要相當(dāng)多的基礎(chǔ)工作的。

例如,可以給定文本字符串、字體大小和字體。但是,當(dāng)用戶鍵入時,F(xiàn)inda將突出顯示匹配項(xiàng):

https:///blog/building-a-fast-electron-app-with-rust/highlighting.mp4

這就意味著我需要處理多個字體和顏色,并跟蹤每個繪制的子字符串的邊界框,以設(shè)置好所有內(nèi)容。

除了渲染之外,我發(fā)現(xiàn)操作系統(tǒng)集成方面也存在著一些困難點(diǎn):

1、建立一個沒有標(biāo)題欄、最小化、最大化、關(guān)閉按鈕的無邊框窗口;

2、后臺運(yùn)行應(yīng)用程序,不在Dock中顯示;

3、通過Quartz Event Services(https://developer.apple.com/documentation/coregraphics/quartz_event_services?language=objc)獲得一個“全局熱鍵”。

關(guān)于第三個困難點(diǎn),在4小時之后,我設(shè)法獲得了關(guān)鍵代碼,但我發(fā)現(xiàn)我需要通過單獨(dú)的一組循環(huán)來查找活動鍵盤映射,于是就放棄了這一想法。

上述都不是真正的“游戲問題”,并且這看起來并不像切換到另一個框架,例如GLUT(OpenGL,https://www./resources/libraries/glut/)會比ggez(SDL)要好。

Electron:之前我已經(jīng)使用Electron構(gòu)建過應(yīng)用程序,而且我知道它會符合Finda的要求。瀏覽器最初是為了布局文本而設(shè)計(jì)的,Electron提供了廣泛的窗口選項(xiàng)(https://github.com/electron/electron/blob/master/docs/api/browser-window.md)和全局快捷方式的一行API(https://github.com/electron/electron/blob/master/docs/api/global-shortcut.md)。

結(jié)構(gòu)

Electron用語用戶界面層,Rust作為二進(jìn)制執(zhí)行并處理所有其他內(nèi)容

當(dāng)Finda打開,并按下一個鍵時:

1) 瀏覽器調(diào)用一個文檔onKeyDown監(jiān)聽器,該監(jiān)聽器將JavaScript keydown事件翻譯為表示事件的普通JavaScript對象,就像是:

{name: 'keydown', key: 'C-g'}

2) 這個JavaScript對象被傳遞給Rust(之后會傳遞更多),Rust返回另一個表示整個應(yīng)用程序狀態(tài)的普通JavaScript對象:

{ query: 'search terms', results: [{label: 'foo', icon: 'bar.png'}, ...], selected_idx: 2, show_overlay: false, ... }

3) 然后將這個JavaScript對象傳遞給React.js,它使用將器實(shí)際呈現(xiàn)給DOM。

在這個架構(gòu)中,有兩點(diǎn)需要注意:

首先,Electron沒有維護(hù)任何一種狀態(tài)。從它的角度來看,整個應(yīng)用程序都是最近事件的函數(shù)。這一點(diǎn)是可能的,因?yàn)镽ust始終維持Finda的內(nèi)部狀態(tài)。

其次,這些步驟發(fā)生在每個用戶交互(keyup和keydown)過程中。因此,為了滿足性能要求,所有三個步驟必須在16ms內(nèi)完成。

INTEROP

其中比較有趣的是第二個步驟,如果從JavaScript調(diào)用Rust,那會是什么樣子?

我們使用了Neon庫,與Rust共同構(gòu)建一個Node.js模塊。

從Electron角度來看,這就像調(diào)用任何其他類型的包裝一樣:

var Native = require('Native');var new_app = Native.step({name: 'keydown', key: 'C-g'});

Rust中這個函數(shù)有一些復(fù)雜,我們來具體分析一下:

pub fn step(call: Call) -> JsResult { let scope = call.scope; let event = &call.arguments.require(scope, 0)?.check::()?; let event_type: String = event .get(scope, 'name')? .downcast::() .unwrap() .value();

JavaScript有幾種語義不能完美映射到Rust的語言語義(例如,參數(shù)對象和動態(tài)變量)。

因此,Neon不會試圖將JS調(diào)用映射到Rust函數(shù)簽名,而是將函數(shù)傳遞給一個Call對象,從中可以提取細(xì)節(jié)。 由于我已經(jīng)編寫了這個函數(shù)的調(diào)用(JS)端,我知道第一個參數(shù)是這里唯一的參數(shù),它是一個JavaScript對象,并且始終有一個與字符串值關(guān)聯(lián)的名稱鍵。

然后,可以使用此event_type字符串將JavaScript對象的“翻譯”的其余部分引導(dǎo)至適當(dāng)?shù)腇inda :: Event枚舉變量:

match event_type.as_str() { 'blur' => finda::step(&mut app, finda::Event::Blur), 'hide' => finda::step(&mut app, finda::Event::Hide), 'show' => finda::step(&mut app, finda::Event::Show), 'keydown' => { let s = event .get(scope, 'key')? .downcast::() .unwrap() .value(); finda::step(&mut app, finda::Event::KeyDown(s)); } ...

這些分支還會調(diào)用finda :: step函數(shù),它將實(shí)際更新應(yīng)用程序狀態(tài)以響應(yīng)事件,例如:更改查詢并返回相關(guān)結(jié)果、打開選定結(jié)果、隱藏Finda等等。

(我會在以后的博客文章中詳細(xì)講解Rust,希望大家繼續(xù)關(guān)注我的博客,或者關(guān)注@lynaghk)

在應(yīng)用程序狀態(tài)更新之后,它需要返回到Electron端進(jìn)行渲染。這個過程看起來與其他方案都很相似,但實(shí)際是在另一個方向上,它是將Rust數(shù)據(jù)結(jié)構(gòu)翻譯成JavaScript數(shù)據(jù)結(jié)構(gòu):

let o = JsObject::new(scope); o.set('show_overlay', JsBoolean::new(scope, app.show_overlay))?; o.set('query', JsString::new(scope, &app.query).unwrap())?; o.set( 'selected_idx', JsNumber::new(scope, app.selected_idx as f64), )?;

在這里,我們首先創(chuàng)建JavaScript對象,該對象將返回到Electron并將一些鍵與某些基本類型相關(guān)聯(lián)。

返回結(jié)果(一個對象類型數(shù)組)需要更多的限制:數(shù)組大小需要事先聲明、Rust結(jié)構(gòu)必須明確列舉出來。但整體來說,還不算太糟糕:

let rs = JsArray::new(scope, app.results.len() as u32);for (idx, r) in app.results.iter().enumerate() { let jsr = JsObject::new(scope); jsr.set('label', JsString::new(scope, &r.label).unwrap())?; if let Some(ref icon) = r.icon { jsr.set('icon', JsString::new(scope, &icon.pathname).unwrap())?; } rs.set(idx as u32, jsr)?; } o.set('results', rs)?;

最后,在該函數(shù)結(jié)束時返回JavaScript對象:

Ok(o)

Neon處理所有的細(xì)節(jié),并將其傳遞給JavaScript端的調(diào)用者。

性能驗(yàn)證

那么,在實(shí)踐中它們的性能表現(xiàn)得如何呢? 在Chrome DevTools的“性能”選項(xiàng)卡(內(nèi)置于Electron中)中,我們可以看到,這是一個單一keypress的典型曲線:

其中的每個步驟都被標(biāo)記:1)將按鍵轉(zhuǎn)換為事件,2)在Rust中處理事件,3)使用React渲染結(jié)果。

首選需要注意的是頂部的綠色條,這表明所有這些都在14毫秒之內(nèi)完成。

其次注意的是Rust的Interop,在其中高亮顯示的Native.step()調(diào)用僅在不到1毫秒之內(nèi)就進(jìn)行完成。

我嘗試在查詢中添加一個字母,那么這一特殊的keydown事件會導(dǎo)致在Finda中進(jìn)行如下步驟,而這些步驟都是在1毫秒內(nèi)完成的:

1、對所有我打開的窗口、Emacs緩沖區(qū)、瀏覽器約20000頁標(biāo)題及URL、~/work/、~/Downloads/和~/Dropbox/文件夾進(jìn)行正則表達(dá)式搜索。

2、根據(jù)質(zhì)量啟發(fā)式(匹配數(shù)量、是否出現(xiàn)在詞語邊界等)對所有這些結(jié)果進(jìn)行排序。

3、將前50個結(jié)果轉(zhuǎn)換為JavaScript并返回。

如果你不相信能有這么快的速度,可以自己下載并嘗試。針對不同的事件,其性能數(shù)據(jù)也有所不同,但這種追蹤是非常典型的:Rust需要幾毫秒來完成實(shí)際工作,大部分時間都是在進(jìn)行渲染,并且整個JavaScript執(zhí)行都會在16毫秒內(nèi)完成。

對性能的繼續(xù)研究

考慮到這些性能指標(biāo),我們可以通過刪除React(也可能是整個DOM)來縮短響應(yīng)時間,而不是使用元素手動處理布局并進(jìn)行渲染。

然而,如果不考慮人類是否能夠區(qū)分出15毫秒的響應(yīng)和5毫秒的響應(yīng)之間的區(qū)別,還是存在一些嚴(yán)重的收益遞減情況的。很可能有某些低級別的操作系統(tǒng)、圖形驅(qū)動程序、LCD硬件影響了響應(yīng)時間。

另外,在Electron中,除了易于使用的內(nèi)置分析 工具之外,DOM和CSS提供了大量的Runtime延展性。打開Inspector后,就有不同的字體、顏色和間距來區(qū)分:https:///blog/building-a-fast-electron-app-with-rust/devtools.mp4。

對于像Finda這樣的完全數(shù)據(jù)驅(qū)動的應(yīng)用程序來說,具有視覺剪影和播放的能力至關(guān)重要。這樣一來,就可以通過在圖形設(shè)計(jì)工具周圍推動像素,來實(shí)現(xiàn)基于搜索的交互。

對我而言,如果沒有Electron和Rust,我就無法制作出Finda的原型并發(fā)布。這二者都是非常棒的技術(shù),在此要感謝所有為他們做出貢獻(xiàn)的人。

總結(jié)

Electron可以輕松構(gòu)建和分發(fā)桌面應(yīng)用程序,讓我擺脫繁瑣的字體渲染細(xì)節(jié)、低級操作系統(tǒng)熱鍵和窗口API。

Rust使得編寫過程快速而安全,低級別的數(shù)據(jù)結(jié)構(gòu)在Rust中就變得很容易,并且我在其引導(dǎo)下,開始以JavaScript/ClojureScript hat的方式來思考內(nèi)存和性能的相關(guān)問題。

最后,我要感謝Nikita Prokopov、Saul Pwanson、Tom Ballinger、Veit Heller、Julia Evans和Bert Muthalaly對本文提出的反饋意見。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多