piskel/src/js/service/HistoryService.js

217 lines
7.1 KiB
JavaScript
Raw Normal View History

(function () {
2014-04-17 03:27:49 +04:00
var ns = $.namespace('pskl.service');
2014-08-12 09:11:23 +04:00
ns.HistoryService = function (piskelController, shortcutService, deserializer) {
this.piskelController = piskelController || pskl.app.piskelController;
this.shortcutService = shortcutService || pskl.app.shortcutService;
this.deserializer = deserializer || pskl.utils.serialization.Deserializer;
2014-08-12 02:30:57 +04:00
2014-04-17 03:27:49 +04:00
this.stateQueue = [];
this.currentUUID = false;
this.lastLoadState = -1;
};
// Force to save a state as a SNAPSHOT
ns.HistoryService.SNAPSHOT = 'SNAPSHOT';
// Default save state
ns.HistoryService.REPLAY = 'REPLAY';
// Period (in number of state saved) between two snapshots
2014-08-12 09:11:23 +04:00
ns.HistoryService.SNAPSHOT_PERIOD = 50;
// Interval/buffer (in milliseconds) between two state load (ctrl+z/y spamming)
2014-08-12 09:11:23 +04:00
ns.HistoryService.LOAD_STATE_INTERVAL = 50;
ns.HistoryService.prototype.init = function () {
2014-08-12 02:30:57 +04:00
$.subscribe(Events.PISKEL_SAVE_STATE, this.onSaveStateEvent.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts;
this.shortcutService.registerShortcut(shortcuts.MISC.UNDO, this.undo.bind(this));
this.shortcutService.registerShortcut(shortcuts.MISC.REDO, this.redo.bind(this));
this.saveState({
type : ns.HistoryService.SNAPSHOT
});
};
ns.HistoryService.prototype.onSaveStateEvent = function (evt, action) {
this.saveState(action);
};
ns.HistoryService.prototype.saveState = function (action) {
2014-04-17 03:27:49 +04:00
var state = {
action : action,
frameIndex : action.state ? action.state.frameIndex : this.piskelController.currentFrameIndex,
layerIndex : action.state ? action.state.layerIndex : this.piskelController.currentLayerIndex,
previousIndex: false,
nextIndex: false
2014-04-17 03:27:49 +04:00
};
var isSnapshot = action.type === ns.HistoryService.SNAPSHOT;
var isAtAutoSnapshotInterval = (this.stateQueue.length - 1) % ns.HistoryService.SNAPSHOT_PERIOD === 0;
if (isSnapshot || isAtAutoSnapshotInterval) {
state.piskel = this.piskelController.serialize(true);
2014-04-17 03:27:49 +04:00
}
this.pushNewState_(state);
$.publish(Events.HISTORY_STATE_SAVED);
};
ns.HistoryService.prototype.getCurrentState = function(){
if(this.currentUUID){
return this.stateQueue[this.currentUUID];
}
else{
return false;
}
};
ns.HistoryService.prototype.pushNewState_ = function(state){
// Generate a random UUID (~1e28 combinations)
var uuid = 'xxxxxx'.replace(/x/g, function(){
return (Math.random() * 36 << 0).toString(36);
});
var currentState = this.getCurrentState();
if(currentState){
// Clear unlinked states
if(currentState.nextIndex){
this.clearBranchingQueue_(currentState.nextIndex);
}
currentState.nextIndex = uuid;
}
state.previousIndex = this.currentUUID;
this.stateQueue[uuid] = state;
this.currentUUID = uuid;
};
ns.HistoryService.prototype.clearBranchingQueue_ = function(index){
while(this.stateQueue[index]){
var next = this.stateQueue[index].nextIndex;
delete(this.stateQueue[index]);
index = next;
}
};
2012-09-16 15:10:05 +04:00
ns.HistoryService.prototype.undo = function () {
var currentState = this.getCurrentState();
if(currentState.previousIndex)
this.loadState(currentState.previousIndex);
};
2012-09-16 15:10:05 +04:00
ns.HistoryService.prototype.redo = function () {
var currentState = this.getCurrentState();
if(currentState.nextIndex)
this.loadState(currentState.nextIndex);
2014-04-17 03:27:49 +04:00
};
ns.HistoryService.prototype.isLoadStateAllowed_ = function (index) {
2014-08-12 09:11:23 +04:00
var timeOk = (Date.now() - this.lastLoadState) > ns.HistoryService.LOAD_STATE_INTERVAL;
var indexInRange = index && this.stateQueue[index];
return timeOk && indexInRange;
};
ns.HistoryService.prototype.getPreviousSnapshotIndex_ = function (index) {
while (this.stateQueue[index] && !this.stateQueue[index].piskel) {
index = this.stateQueue[index].previousIndex;
}
return index;
};
2014-04-17 03:27:49 +04:00
ns.HistoryService.prototype.loadState = function (index) {
try {
if (this.isLoadStateAllowed_(index)) {
this.lastLoadState = Date.now();
var snapshotIndex = this.getPreviousSnapshotIndex_(index);
if (!snapshotIndex) {
throw 'Could not find previous SNAPSHOT saved in history stateQueue';
}
var serializedPiskel = this.getSnapshotFromState_(snapshotIndex);
var onPiskelLoadedCb = this.onPiskelLoaded_.bind(this, index, snapshotIndex);
this.deserializer.deserialize(serializedPiskel, onPiskelLoadedCb);
}
} catch (error) {
console.error('[CRITICAL ERROR] : Unable to load a history state.');
this.logError_(error);
this.stateQueue = [];
this.currentUUID = false;
}
};
2014-04-17 03:27:49 +04:00
ns.HistoryService.prototype.logError_ = function (error) {
if (typeof error === 'string') {
console.error(error);
} else {
console.error(error.message);
console.error(error.stack);
}
};
ns.HistoryService.prototype.getSnapshotFromState_ = function (stateIndex) {
var state = this.stateQueue[stateIndex];
var piskelSnapshot = state.piskel;
2014-04-17 03:27:49 +04:00
// If the snapshot is stringified, parse it and backup the result for faster access next time
// FIXME : Memory consumption might go crazy if we keep unpacking big piskels indefinitely
// ==> should ensure I remove some of them :)
if (typeof piskelSnapshot === 'string') {
piskelSnapshot = JSON.parse(piskelSnapshot);
state.piskel = piskelSnapshot;
2014-04-17 03:27:49 +04:00
}
return piskelSnapshot;
};
2014-04-17 03:27:49 +04:00
ns.HistoryService.prototype.onPiskelLoaded_ = function (index, snapshotIndex, piskel) {
var originalSize = this.getPiskelSize_();
piskel.setDescriptor(this.piskelController.piskel.getDescriptor());
// propagate save path to the new piskel instance
piskel.savePath = this.piskelController.piskel.savePath;
this.piskelController.setPiskel(piskel);
2014-04-17 03:27:49 +04:00
var walkingIndex = snapshotIndex;
do{
walkingIndex = this.stateQueue[walkingIndex].nextIndex;
var state = this.stateQueue[walkingIndex];
this.setupState(state);
2014-04-17 03:27:49 +04:00
this.replayState(state);
} while(walkingIndex && walkingIndex != index);
2014-04-17 03:27:49 +04:00
2014-08-12 09:11:23 +04:00
// Should only do this when going backwards
var next = this.stateQueue[index].nextIndex;
if (next) {
this.setupState(this.stateQueue[next]);
}
this.currentUUID = index;
$.publish(Events.PISKEL_RESET);
$.publish(Events.HISTORY_STATE_LOADED);
if (originalSize !== this.getPiskelSize_()) {
$.publish(Events.FRAME_SIZE_CHANGED);
}
};
ns.HistoryService.prototype.getPiskelSize_ = function () {
return this.piskelController.getWidth() + 'x' + this.piskelController.getHeight();
2014-04-17 03:27:49 +04:00
};
ns.HistoryService.prototype.setupState = function (state) {
this.piskelController.setCurrentFrameIndex(state.frameIndex);
this.piskelController.setCurrentLayerIndex(state.layerIndex);
};
2014-04-17 03:27:49 +04:00
ns.HistoryService.prototype.replayState = function (state) {
var action = state.action;
var type = action.type;
var layer = this.piskelController.getLayerAt(state.layerIndex);
var frame = layer.getFrameAt(state.frameIndex);
action.scope.replay(frame, action.replay);
};
})();