上篇將HT for Web的3D拓撲彈力布局的算法運行在Web Workers后台(http://www.hightopo.com/blog/70.html),這篇我們將進一步折騰,將算法運行到真正的后台: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方式,實現真正的后台多核任務處理