上篇将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到客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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监听服务器推送过来的自动布局结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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/](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Flocalhost%3A8036%2F)' ); 
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方式,实现真正的后台多核任务处理
 
  
  
  
 
 
  
 
 
 