上篇將3D彈力佈局的演算法執行在Web Workers後臺,這篇我們將進一步折騰,將演算法執行到真正的後臺:Node.js,事先申明Node.js篇和Web Workers篇一樣,在這個應用場景下並不能提高效能,純粹為了折騰好玩,當然也不會白玩,人生就在折騰中,只有折騰才能真正成長。
核心實現程式碼和Web Workers篇基本一致,唯一區別在於前後臺互動的方式上,worker通過postMessage和addEventListener('message' 就可以傳送和接收訊息,對於真正分離前後臺的Node.js自然沒那麼簡單了,我採用了Socket.io通訊框架,Socket.io讓長連線通訊變得無比簡單,和Web Workers的通訊幾乎一樣的容易了,Socket.io的用法下圖一目瞭然:
Node.js後臺程式碼如下,通過require引入HT和Socket.io相關類庫,io = require('socket.io').listen(8036)構建出一個監聽在8036埠的服務,通過io.sockets.on('connection'等著客戶端頁面來建立的socket通訊,通過socket.on('moveMap',監聽客戶端發過來的圖片節點拖拽變化資訊進行同步,通過 socket.emit('result', result);傳送自動佈局演算法的運算結果push到客戶端。
io = require('socket.io').listen(8036); ht = require('ht.js').ht; require("ht-forcelayout.js"); reloadModel = require("util.js").reloadModel; io.sockets.on('connection', function (socket) { var dataModel = new ht.DataModel(), forceLayout = new ht.layout.Force3dLayout(dataModel); forceLayout.onRelaxed = function(){ var result = {}; dataModel.each(function(data){ if(data instanceof ht.Node){ result[data._id] = data.p3(); } }); socket.emit('result', result); }; forceLayout.start(); socket.on('moveMap', function (moveMap) { dataModel.sm().cs(); for(var id in moveMap){ var data = dataModel.getDataById(id); if(data){ data.p3(moveMap[id]); dataModel.sm().as(data); } } }); socket.on('reload', function (data) { reloadModel(dataModel, data); }); });
客戶端的程式碼需要通過引入Socket.io客戶端類庫,通過socket = io.connect('http://localhost:8036/')連結伺服器獲得握手鍊接socket物件,剩下的程式碼就是同socket.emit傳送客戶端拖拽資訊,以及socket.on監聽伺服器推送過來的自動佈局結果:
g3d.mi(function(evt){ if(evt.kind === 'betweenMove'){ moveMap = {}; g3d.sm().each(function(data){ if(data instanceof ht.Node){ moveMap[data._id] = data.p3(); } }); socket.emit('moveMap', moveMap); } }); socket = io.connect('http://localhost:8036/'); socket.on('result', function (result) { for(var id in result){ var data = dataModel.getDataById([id]); if(data && !g3d.isSelected(data)){ data.p3(result[id]); } } });
幾個注意點:
1、首選和Web Workers一樣,跑在Node.js的類庫肯定不能操作window和document之類的頁面特定元素物件,從這點說很多考慮不周全的類庫會把自己限制死只能在頁面主執行緒執行,這點HT for Web考慮得很周到,不僅ht.js包括所有ht-forcelayout.js外掛都是可運在Web Workers和Node.js的非GUI環境,因為我也常需要ht.js執行在後臺直接將DataModel的資料和前臺進行JSON的資料格式轉換儲存。
2、Util.js定義的reloadModel函式我增加了this.reloadModel = reloadModel;的邏輯,這樣才能在Node.js後臺程式碼reloadModel = require("util.js").reloadModel; 這樣的方式得到該函式進行呼叫,細節可以參考 http://nodejs.org/api/modules.html 的章節
3、這個例子是有缺陷的,以下視訊播放過程你會發現,我開啟了兩個頁面,這樣就會有兩個socket分別連線後臺Node.js,而Node.js預設是單執行緒的,如果正在一個請求函式密集運算處理,則其他請求只能排隊等待處理,這也是視訊中我拖拽一個頁面佈局是,另一個頁面無法操作的原因。當然你可以改進demo,採用http://nodejs.org/api/cluster.html的cluster方式,實現真正的後臺多核任務處理