本章講解歷史邏輯處理editor\js\History.js,這個文件的主要作用是維護所有的操作命令,已達到可以撤銷、重做相關命令的目的,其中有3點需要重點說一下,第一點是腳本的編輯操作時不能撤銷、重做的,然後就是兩次操作的時間很短時(小於500毫秒)也是不能撤銷和重做的,最後一點是命令可以存儲在內存中,也可以序列化到json中,對命令佔用大量內存時比較好。
//歷史命令存儲棧
History = function ( editor ) {
this.editor = editor; //保存全局編輯器
this.undos = []; //撤銷隊列
this.redos = []; //重做隊列
this.lastCmdTime = new Date(); //上次的時間
this.idCounter = 0; //命令id索引
this.historyDisabled = false; //歷史啓用、禁用
this.config = editor.config; //配置
//Set editor-reference in Command,爲甚me要在這裏設置,因爲沒有更合適的地方,命令主要是爲撤銷、重做封裝的
Command( editor ); //將editor設置爲Command的靜態成員
// signals
var scope = this;
this.editor.signals.startPlayer.add( function () { //開始播放時處理
scope.historyDisabled = true; //禁用歷史
} );
this.editor.signals.stopPlayer.add( function () { //停止播放時處理
scope.historyDisabled = false; //啓用歷史
} );
};
History.prototype = {
//執行命令
execute: function ( cmd, optionalName ) {
//彈出最後一次命令
var lastCmd = this.undos[ this.undos.length - 1 ];
//時間間隔
var timeDifference = new Date().getTime() - this.lastCmdTime.getTime();
var isUpdatableCmd = lastCmd && //上次命令存在
lastCmd.updatable && //上次命令也是可以更新的
cmd.updatable && //本次命令是可以更新的
lastCmd.object === cmd.object && //這次的命令關聯的對象與上次的是否爲同一個
lastCmd.type === cmd.type && //這次的命令類型與上次的是否是同一種類型
lastCmd.script === cmd.script && //這次的腳本和上次的腳本是否爲同一個腳本
lastCmd.attributeName === cmd.attributeName; //這次的屬性名稱和上次的是否爲同一個名稱(例如一個腳本修改了多次)
if ( isUpdatableCmd && cmd.type === "SetScriptValueCommand" ) { //腳本命令類型
// When the cmd.type is "SetScriptValueCommand" the timeDifference is ignored
//忽略命令,腳本的編輯操作不能被撤銷
lastCmd.update( cmd );
cmd = lastCmd;
} else if ( isUpdatableCmd && timeDifference < 500 ) { //多次撤銷命令的時間間隔很短??
//忽略命令,腳本的編輯操作不能被撤銷
lastCmd.update( cmd );
cmd = lastCmd;
} else { //該命令不可更新
// the command is not updatable and is added as a new part of the history
//該命令不可更新,將作爲歷史記錄的新部分添加
this.undos.push( cmd ); //存放到可撤銷的命令棧中
cmd.id = ++ this.idCounter; //命令id
}
//命令的名稱
cmd.name = ( optionalName !== undefined ) ? optionalName : cmd.name;
cmd.execute(); //執行命令
cmd.inMemory = true; //命令在內存中存放??
if ( this.config.getKey( 'settings/history' ) ) { //獲取歷史相關的配置信息
//序列化命令
cmd.json = cmd.toJSON(); // serialize the cmd immediately after execution and append the json to the cmd
}
//執行完成的時間
this.lastCmdTime = new Date();
// clearing all the redo-commands
// 清空所有的重做命令(當添加一個新命令後,原來的所有撤銷的命令就不存在了)
this.redos = [];
//歷史改變了
this.editor.signals.historyChanged.dispatch( cmd );
},
//撤銷
undo: function () {
//播放時是不能撤銷操作
if ( this.historyDisabled ) {
alert( "Undo/Redo disabled while scene is playing." );
return;
}
var cmd = undefined;
if ( this.undos.length > 0 ) {
// /彈出撤銷命令
cmd = this.undos.pop();
//命令不在內存中,將命令從json文件中提取出來,這一部分屬於優化內容,可以保存到indexDB中
if ( cmd.inMemory === false ) {
//反序列化命令
cmd.fromJSON( cmd.json );
}
}
if ( cmd !== undefined ) {
//執行撤銷命令
cmd.undo();
//將命令壓入到重做命令列表中
this.redos.push( cmd );
//歷史命令改變,消息派發
this.editor.signals.historyChanged.dispatch( cmd );
}
return cmd;
},
//重做
redo: function () {
//播放時是不能重做操作
if ( this.historyDisabled ) {
alert( "Undo/Redo disabled while scene is playing." );
return;
}
var cmd = undefined;
if ( this.redos.length > 0 ) {
//彈出重做命令
cmd = this.redos.pop();
//如果命令不在內存中,反序列化命令
if ( cmd.inMemory === false ) {
cmd.fromJSON( cmd.json );
}
}
if ( cmd !== undefined ) {
//執行命令
cmd.execute();
//命令重新設置到撤銷列表
this.undos.push( cmd );
//歷史命令改變,消息派發
this.editor.signals.historyChanged.dispatch( cmd );
}
return cmd;
},
//轉換到json
toJSON: function () {
var history = {};
history.undos = [];
history.redos = [];
//啓用歷史存儲
if ( ! this.config.getKey( 'settings/history' ) ) {
return history;
}
// Append Undos to History
// 存儲撤銷命令列表
for ( var i = 0 ; i < this.undos.length; i ++ ) {
if ( this.undos[ i ].hasOwnProperty( "json" ) ) {
history.undos.push( this.undos[ i ].json );
}
}
// Append Redos to History
// 存儲重做命令列表
for ( var i = 0 ; i < this.redos.length; i ++ ) {
if ( this.redos[ i ].hasOwnProperty( "json" ) ) {
history.redos.push( this.redos[ i ].json );
}
}
return history;
},
//json中提取數據
fromJSON: function ( json ) {
if ( json === undefined ) return;
for ( var i = 0; i < json.undos.length; i ++ ) {
var cmdJSON = json.undos[ i ];
var cmd = new window[ cmdJSON.type ](); // creates a new object of type "json.type"
cmd.json = cmdJSON;
cmd.id = cmdJSON.id;
cmd.name = cmdJSON.name;
this.undos.push( cmd );
this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter; // set last used idCounter
}
for ( var i = 0; i < json.redos.length; i ++ ) {
var cmdJSON = json.redos[ i ];
var cmd = new window[ cmdJSON.type ](); // creates a new object of type "json.type"
cmd.json = cmdJSON;
cmd.id = cmdJSON.id;
cmd.name = cmdJSON.name;
this.redos.push( cmd );
this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter; // set last used idCounter
}
// Select the last executed undo-command
this.editor.signals.historyChanged.dispatch( this.undos[ this.undos.length - 1 ] );
},
//清除命令
clear: function () {
//清空所有的信息
this.undos = [];
this.redos = [];
this.idCounter = 0;
//歷史改變了
this.editor.signals.historyChanged.dispatch();
},
//恢復到某一個操作狀態
goToState: function ( id ) {
//歷史功能是否啓用
if ( this.historyDisabled ) {
alert( "Undo/Redo disabled while scene is playing." );
return;
}
//禁用場景圖消息、歷史改變消息
this.editor.signals.sceneGraphChanged.active = false;
this.editor.signals.historyChanged.active = false;
//獲取最後一個命令
var cmd = this.undos.length > 0 ? this.undos[ this.undos.length - 1 ] : undefined; // next cmd to pop
//沒有命令,或者命令id大於當前撤銷隊列的數量,彈出所有撤銷過的命令
if ( cmd === undefined || id > cmd.id ) {
//撤銷重做命令
cmd = this.redo();
while ( cmd !== undefined && id > cmd.id ) {
cmd = this.redo();
}
} else {
while ( true ) {
cmd = this.undos[ this.undos.length - 1 ]; // next cmd to pop
if ( cmd === undefined || id === cmd.id ) break;
this.undo();
}
}
//重新啓用場景圖改變消息、歷史改變消息
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.historyChanged.active = true;
this.editor.signals.sceneGraphChanged.dispatch();
this.editor.signals.historyChanged.dispatch( cmd );
},
//啓用序列化功能
enableSerialization: function ( id ) {
/**
* because there might be commands in this.undos and this.redos
* which have not been serialized with .toJSON() we go back
* to the oldest command and redo one command after the other
* while also calling .toJSON() on them.
*/
this.goToState( - 1 );
//禁用場景圖消息、歷史消息
this.editor.signals.sceneGraphChanged.active = false;
this.editor.signals.historyChanged.active = false;
var cmd = this.redo();
while ( cmd !== undefined ) {
if ( ! cmd.hasOwnProperty( "json" ) ) {
//命令序列化
cmd.json = cmd.toJSON();
}
cmd = this.redo();
}
//啓用消息
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.historyChanged.active = true;
//恢復到某一個操作狀態
this.goToState( id );
}
};