lots of stuff connected with tags and performance...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2013-12-21 09:49:15 +04:00
parent 4a45ed536f
commit 5a687e0307
6 changed files with 180 additions and 29 deletions

View File

@ -147,7 +147,7 @@ function setupBookmarks(viewer){
return viewer return viewer
.on('sortedImages', function(){ .on('sortedImages', function(){
BOOKMARKS.sort(imageOrderCmp) BOOKMARKS = fastSortGIDsByOrder(BOOKMARKS)
}) })
} }
SETUP_BINDINGS.push(setupBookmarks) SETUP_BINDINGS.push(setupBookmarks)

View File

@ -536,6 +536,54 @@ Array.prototype.binSearch = function(target, cmp, get){
*/ */
// This is a cheating fast sort...
//
// By cheating we might use more memory -- this is both not in-place
// and may use quite a bit of memory...
//
// The gain is that this is SIGNIFICANTLY faster than using
// .sort(imageOrderCmp)...
//
// The complexity here is O(N) where N is DATA.order.length rather than
// gids.length vs. O(n nog n) for the .sort(..), but the processing overhead
// is significantly smaller...
//
// Here are a couple of test runs:
//
// var t0 = Date.now()
// getRibbonGIDs()
// .slice(0, 2000)
// .sort(imageOrderCmp)
// console.log('T:', Date.now()-t0)
// >>> T: 4126
//
// var t0 = Date.now()
// fastSortGIDsByOrder(
// getRibbonGIDs()
// .slice(0,2000))
// console.log('T:', Date.now()-t0)
// >>> T: 171
//
// On the down side, this has some memory overhead -- ~ N - n * ref
//
function fastSortGIDsByOrder(gids, data){
data = data == null ? DATA : data
var order = data.order
var res = []
// insert the gids to their order positions...
gids.forEach(function(gid){
res[order.indexOf(gid)] = gid
})
// clear out the nulls...
return res.filter(function(e){
return e != null
})
}
// Base URL interface... // Base URL interface...
// //
// NOTE: changing a base URL will trigger a baseURLChanged event... // NOTE: changing a base URL will trigger a baseURLChanged event...
@ -638,12 +686,16 @@ function getGIDRibbonIndex(gid, data){
// - number - ribbon index // - number - ribbon index
// - gid // - gid
// - image // - image
function getRibbonGIDs(a, data){ function getRibbonGIDs(a, no_clone, data){
data = data == null ? DATA : data data = data == null ? DATA : data
if(typeof(a) == typeof(123)){ if(typeof(a) == typeof(123)){
return data.ribbons[a].slice() return data.ribbons[a].slice()
} }
return data.ribbons[getGIDRibbonIndex(a, data)].slice() var res = data.ribbons[getGIDRibbonIndex(a, data)]
if(no_clone){
return res
}
return res.slice()
} }
@ -1286,6 +1338,10 @@ function makeNextFromListAction(get_closest, get_list, restrict_to_ribbon){
// see makeNextFromListAction(..) above for documentation... // see makeNextFromListAction(..) above for documentation...
//
// XXX try a new technique:
// - before calling getImageBefore(..) remove the curent gid form
// target list...
function makePrevFromListAction(get_closest, get_list, restrict_to_ribbon){ function makePrevFromListAction(get_closest, get_list, restrict_to_ribbon){
get_closest = get_closest == null ? getGIDBefore : get_closest get_closest = get_closest == null ? getGIDBefore : get_closest
get_list = get_list == null ? getRibbonGIDs : get_list get_list = get_list == null ? getRibbonGIDs : get_list
@ -2113,7 +2169,8 @@ function showImage(gid){
// NOTE: if no_reload_viewer is true, then no re-rendering is triggered. // NOTE: if no_reload_viewer is true, then no re-rendering is triggered.
function updateRibbonOrder(no_reload_viewer){ function updateRibbonOrder(no_reload_viewer){
for(var i=0; i < DATA.ribbons.length; i++){ for(var i=0; i < DATA.ribbons.length; i++){
DATA.ribbons[i].sort(imageOrderCmp) //DATA.ribbons[i].sort(imageOrderCmp)
DATA.ribbons[i] = fastSortGIDsByOrder(DATA.ribbons[i])
} }
if(!no_reload_viewer){ if(!no_reload_viewer){
reloadViewer(true) reloadViewer(true)

View File

@ -588,6 +588,10 @@ var KEYBOARD_CONFIG = {
function(){ prevBookmark() }), function(){ prevBookmark() }),
']': doc('Next bookmarked image', ']': doc('Next bookmarked image',
function(){ nextBookmark() }), function(){ nextBookmark() }),
'{': doc('Previous unsorted section edge',
function(){ prevUnsortedSection() }),
'}': doc('Next unsorted section edge',
function(){ nextUnsortedSection() }),
S: { S: {
default: doc('Start slideshow', default: doc('Start slideshow',

View File

@ -164,7 +164,7 @@ var updateSelectedImageMark = makeMarkUpdater(
// not exist, as there is no way to distinguish between the two // not exist, as there is no way to distinguish between the two
// situations the cleanup is optional... // situations the cleanup is optional...
function cropMarkedImages(keep_ribbons, keep_unloaded_gids){ function cropMarkedImages(keep_ribbons, keep_unloaded_gids){
var marked = MARKED.slice()//.sort(imageOrderCmp) var marked = MARKED.slice()
cropDataTo(marked, keep_ribbons, keep_unloaded_gids) cropDataTo(marked, keep_ribbons, keep_unloaded_gids)
@ -249,7 +249,8 @@ function toggleAllMarks(action, mode){
if(action == 'on'){ if(action == 'on'){
var _update = function(e){ var _update = function(e){
if(MARKED.indexOf(e) < 0){ if(MARKED.indexOf(e) < 0){
insertGIDToPosition(e, MARKED) //insertGIDToPosition(e, MARKED)
MARKED.push(e)
updated.push(e) updated.push(e)
} }
} }
@ -274,6 +275,8 @@ function toggleAllMarks(action, mode){
res.forEach(_update) res.forEach(_update)
MARKED = fastSortGIDsByOrder(MARKED)
updateImages(updated) updateImages(updated)
$('.viewer').trigger('togglingMarks', [updated, action]) $('.viewer').trigger('togglingMarks', [updated, action])
@ -310,12 +313,14 @@ function invertImageMarks(){
var i = MARKED.indexOf(e) var i = MARKED.indexOf(e)
if(i == -1){ if(i == -1){
on.push(e) on.push(e)
insertGIDToPosition(e, MARKED) MARKED.push(e)
//insertGIDToPosition(e, MARKED)
} else { } else {
off.push(e) off.push(e)
MARKED.splice(i, 1) MARKED.splice(i, 1)
} }
}) })
MARKED = fastSortGIDsByOrder(MARKED)
updateImages(ribbon) updateImages(ribbon)
$('.viewer') $('.viewer')
@ -348,7 +353,8 @@ function toggleMarkBlock(image){
} }
// do the toggle... // do the toggle...
if(state){ if(state){
insertGIDToPosition(e, MARKED) //insertGIDToPosition(e, MARKED)
MARKED.push(e)
} else { } else {
MARKED.splice(MARKED.indexOf(e), 1) MARKED.splice(MARKED.indexOf(e), 1)
} }
@ -364,6 +370,8 @@ function toggleMarkBlock(image){
var right = ribbon.slice(i+1) var right = ribbon.slice(i+1)
$.each(right, _convert) $.each(right, _convert)
MARKED = fastSortGIDsByOrder(MARKED)
updateImages(updated) updateImages(updated)
$('.viewer') $('.viewer')
@ -396,7 +404,7 @@ function shiftMarkedImages(direction, mode, new_ribbon){
// shift all marked images... // shift all marked images...
} else { } else {
var marked = MARKED.slice() var marked = MARKED.slice()
// remove all the marked images form all the ribbons... // remove all the marked images form all other ribbons...
$.each(DATA.ribbons, function(ribbon){ $.each(DATA.ribbons, function(ribbon){
$.each(marked, function(e){ $.each(marked, function(e){
var i = ribbon.indexOf(e) var i = ribbon.indexOf(e)
@ -420,7 +428,7 @@ function shiftMarkedImages(direction, mode, new_ribbon){
// add marked to existing ribbon... // add marked to existing ribbon...
} else { } else {
cur += direction == 'next' ? 1 : -1 cur += direction == 'next' ? 1 : -1
DATA.ribbons[cur] = DATA.ribbons[cur].concat(marked).sort(cmp) DATA.ribbons[cur] = fastSortGIDsByOrder(DATA.ribbons[cur].concat(marked))
} }
// remove empty ribbons... // remove empty ribbons...
@ -557,7 +565,7 @@ var loadFileMarks = makeFileLoader(
if(DATA.version == '2.0'){ if(DATA.version == '2.0'){
setTimeout(function(){ setTimeout(function(){
var t0 = Date.now() var t0 = Date.now()
MARKED.sort(imageOrderCmp) MARKED = fastSortGIDsByOrder(MARKED)
var t1 = Date.now() var t1 = Date.now()
// XXX is this the correct way to do this??? // XXX is this the correct way to do this???

View File

@ -177,7 +177,10 @@ function getClosestGIDs(gid){
function reverseImageOrder(){ function reverseImageOrder(){
DATA.order.reverse() DATA.order.reverse()
updateRibbonOrder() DATA.ribbons.forEach(function(r){
r.reverse()
})
reloadViewer(true)
} }

View File

@ -20,6 +20,10 @@ var UNSORTED_TAG = 'unsorted'
var TAGS = {} var TAGS = {}
var TAGS_FILE_DEFAULT = 'tags.json'
var TAGS_FILE_PATTERN = /^[0-9]*-tags.json$/
/*********************************************************************/ /*********************************************************************/
@ -27,6 +31,8 @@ function buildTagsFromImages(tagset, images){
tagset = tagset == null ? TAGS : tagset tagset = tagset == null ? TAGS : tagset
images = images == null ? IMAGES : images images = images == null ? IMAGES : images
var order = DATA.order
for(var gid in images){ for(var gid in images){
var tags = images[gid].tags var tags = images[gid].tags
// no tags in this image... // no tags in this image...
@ -40,10 +46,19 @@ function buildTagsFromImages(tagset, images){
} }
// only update if not tagged... // only update if not tagged...
if(tagset[tag].indexOf(gid) < 0){ if(tagset[tag].indexOf(gid) < 0){
tagset[tag].push(gid) // NOTE: this is cheating, but it's ~5x faster than
// insertGIDToPosition(..) but still 10^2 slower
// than .push(..) (unsorted tags)
tagset[tag][order.indexOf(gid)] = gid
} }
}) })
} }
// cleanup...
for(var tag in tagset){
tagset[tag] = tagset[tag].filter(function(e){ return e != null })
}
return tagset return tagset
} }
@ -93,8 +108,6 @@ function addTag(tags, gid, tagset, images){
tagset[tag] = set tagset[tag] = set
} }
if(set.indexOf(gid) < 0){ if(set.indexOf(gid) < 0){
//set.push(gid)
//set.sort()
insertGIDToPosition(gid, set) insertGIDToPosition(gid, set)
} }
@ -193,7 +206,7 @@ function tagSelectAND(tags, from, no_sort, tagset){
return res == null return res == null
? [] ? []
: res.filter(function(gid){ : res.filter(function(gid){
// skip unloaded from... // skip unloaded...
return from.indexOf(gid) >= 0 return from.indexOf(gid) >= 0
}) })
} }
@ -231,10 +244,15 @@ function tagSelectAND(tags, from, no_sort, tagset){
} }
// populate res... // populate res...
if(gid != null && from.indexOf(gid) >= 0){ if(gid != null && from.indexOf(gid) >= 0){
no_sort ? res.push(gid) : insertGIDToPosition(gid, res) //no_sort == true ? res.push(gid) : insertGIDToPosition(gid, res)
res.push(gid)
} }
}) })
if(!no_sort){
fastSortGIDsByOrder(res)
}
return res return res
} }
@ -271,16 +289,11 @@ function tagSelectOR(tags, from, no_sort, tagset){
} }
/*
// XXX don't remember the semantics... /**********************************************************************
function getRelatedTags(){ * List oriented tag operations...
}
*/ */
/*********************************************************************/
function tagList(list, tags){ function tagList(list, tags){
list.forEach(function(gid){ list.forEach(function(gid){
addTag(tags, gid) addTag(tags, gid)
@ -353,18 +366,22 @@ function unmarkTagged(tags){
// //
// Essentially this will list tag block borders. // Essentially this will list tag block borders.
// //
// NOTE: this will consider each gids's ribbon context rather than the
// straight order context...
// XXX this is slow...
function listTagsAtGapsFrom(tags, gids){ function listTagsAtGapsFrom(tags, gids){
gids = gids == null ? getLoadedGIDs() : gids gids = gids == null ? getLoadedGIDs() : gids
var list = tagSelectAND(tags, gids) var list = tagSelectAND(tags, gids)
var res = [] var res = []
list.forEach(function(gid){ list.forEach(function(gid){
var i = gids.indexOf(gid) var ribbon = DATA.ribbons[getGIDRibbonIndex(gid)]
var i = ribbon.indexOf(gid)
// add the current gid to the result iff one or both gids // add the current gid to the result iff one or both gids
// adjacent to it are not in the list... // adjacent to it are not in the list...
if(list.indexOf(gids[i-1]) < 0 if(list.indexOf(ribbon[i-1]) < 0
|| list.indexOf(gids[i+1]) < 0){ || list.indexOf(ribbon[i+1]) < 0){
res.push(gid) res.push(gid)
} }
}) })
@ -373,6 +390,44 @@ function listTagsAtGapsFrom(tags, gids){
} }
// XXX these are still slow -- there is no need to re-index the whole
// data on each jump...
function nextGapEdge(tags, gids){
var res = getGIDAfter(getImageGID(), listTagsAtGapsFrom(tags, gids))
if(res == null){
flashIndicator('end')
return getImage()
}
return showImage(res)
}
function prevGapEdge(tags, gids){
var cur = getImageGID()
var targets = listTagsAtGapsFrom(tags, gids)
// drop the current elem if it's in the list...
var i = targets.indexOf(cur)
if(i >= 0){
targets.splice(i, 1)
}
var res = getGIDBefore(cur, targets)
if(res == null){
flashIndicator('start')
return getImage()
}
return showImage(res)
}
function nextUnsortedSection(){
return nextGapEdge('unsorted')
}
function prevUnsortedSection(){
return prevGapEdge('unsorted')
}
/*********************************************************************/ /*********************************************************************/
@ -389,6 +444,30 @@ function cropTagged(tags, keep_ribbons, keep_unloaded_gids){
/**********************************************************************
* Files...
*/
// XXX need to detect if we loaded the tags and if so not call
// buildTagsFromImages(..)...
var loadFileTags = makeFileLoader(
'Tags',
TAGS_FILE_DEFAULT,
TAGS_FILE_PATTERN,
function(data){
TAGS = data
})
// Save image marks to file
var saveFileMarks = makeFileSaver(
TAGS_FILE_DEFAULT,
function(){
return TAGS
})
/********************************************************************** /**********************************************************************
* Setup... * Setup...
*/ */
@ -411,7 +490,7 @@ function setupTags(viewer){
}) })
} }
SETUP_BINDINGS.push(setupTags) //SETUP_BINDINGS.push(setupTags)
// Setup the unsorted image state managers... // Setup the unsorted image state managers...