From 1745ae6205b350f36537cd36b8805f56b3f11895 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 29 May 2013 10:23:23 +0100 Subject: [PATCH 1/2] update batchTxn to be a callback instead of object, put batch operations back in for scoring & cron. hoping this will cut back on the 4000ms issue, quite an emergency that --- src/app/character.coffee | 98 +++++++-------------------------------- src/app/index.coffee | 60 +++++++++++------------- src/app/misc.coffee | 51 ++++++++++++-------- src/app/tasks.coffee | 7 +-- src/server/private.coffee | 17 ++++--- 5 files changed, 87 insertions(+), 146 deletions(-) diff --git a/src/app/character.coffee b/src/app/character.coffee index a345fec55c..08a309e66d 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -1,6 +1,8 @@ browser = require './browser' items = require './items' algos = require 'habitrpg-shared/script/algos' +misc = require './misc' +helpers = require 'habitrpg-shared/script/helpers' moment = require 'moment' _ = require 'lodash' @@ -31,26 +33,17 @@ module.exports.app = (appExports, model) -> items.updateStore(model) appExports.reset = (e, el) -> - batch = new BatchUpdate(model) - batch.startTransaction() - taskTypes = ['habit', 'daily', 'todo', 'reward'] - batch.set 'tasks', {} - taskTypes.forEach (type) -> batch.set "#{type}Ids", [] - #batch.set 'balance', 1 if user.get('balance') < 1 #only if they haven't manually bought gems - - # Reset stats - batch.set 'stats.hp', 50 - batch.set 'stats.lvl', 1 - batch.set 'stats.gp', 0 - batch.set 'stats.exp', 0 - # Reset items - batch.set 'items.armor', 0 - batch.set 'items.weapon', 0 - batch.set 'items.head', 0 - batch.set 'items.shield', 0 - + misc.batchTxn model, (uObj, paths) -> + taskTypes = ['habit', 'daily', 'todo', 'reward'] + uObj.tasks = {}; paths['tasks'] = true + taskTypes.forEach (type) -> uObj["#{type}Ids"] = []; paths["#{type}Ids"] = true + # Reset stats + [uObj.stats.hp, uObj.stats.lvl, uObj.stats.gp, uObj.stats.exp] = [50, 1, 0, 0] + # Reset items + [uObj.items.armor, uObj.items.weapon, uObj.items.head, uObj.items.shield] = [0, 0, 0, 0] + ['stats.hp', 'stats.lvl', 'stats.gp', 'stats.exp', 'items.armor', 'items.weapon', 'items.head', 'items.shield'].forEach (path) -> + paths[path] = true items.updateStore(model) - batch.commit() browser.resetDom(model) appExports.closeNewStuff = (e, el) -> @@ -69,70 +62,15 @@ module.exports.app = (appExports, model) -> user.set 'preferences.armorSet', $(el).attr('data-value') appExports.restoreSave = (e, el) -> - batch = new BatchUpdate(model) - batch.startTransaction() - $('#restore-form input').each -> - batch.set $(this).attr('data-for'), parseInt($(this).val() || 1) - batch.commit() + misc.batchTxn model, (uObj, paths) -> + $('#restore-form input').each -> + [path, val] = [$(this).attr('data-for'), parseInt($(this).val() || 1)] + helpers.dotSet(path, val, uObj); paths[path] = true + debugger appExports.toggleHeader = (e, el) -> user.set 'preferences.hideHeader', !user.get('preferences.hideHeader') appExports.deleteAccount = (e, el) -> model.del "users.#{user.get('id')}", -> - window.location.href = "/logout" - -module.exports.BatchUpdate = BatchUpdate = (model) -> - user = model.at("_user") - transactionInProgress = false - obj = null - updates = {} - - { - user: user - - obj: -> - obj ?= model.get 'users.'+user.get('id') - return obj - - startTransaction: -> - # start a batch transaction - nothing between now and @commit() will be set immediately - transactionInProgress = true - model._dontPersist = true - @obj() - - ### - Handles updating the user model. If this is an en-mass operation (eg, server cron), changes are queued - but not actually set to the model. It also modifies userObj in case you need to access properties manually later. - If transaction not in progress, it just runs standard model.set() - ### - set: (path, val) -> - updates[path] = val if transactionInProgress - user.set(path, val) - - ### - Hack to get around dom bindings being lost if parent objects are replaced whole-sale - eg, user.set('stats', {hp:50, exp:10...}) will break dom bindings, but user.set('stats.hp',50) is ok - ### - setStats: (stats) -> - stats ?= obj.stats - that = @ - _.each Object.keys(stats), (key) -> that.set "stats.#{key}", stats[key]; true - -# queue: (path, val) -> -# # Special function for setting object properties by string dot-notation. See http://stackoverflow.com/a/6394168/362790 -# arr = path.split('.') -# arr.reduce (curr, next, index) -> -# if (arr.length - 1) == index -# curr[next] = val -# curr[next] -# , obj - - commit: -> - model._dontPersist = false - # some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js - # pass true if we have levelled to supress xp notification - user.set "update__", updates - transactionInProgress = false - updates = {} - } + location.href = "/logout" \ No newline at end of file diff --git a/src/app/index.coffee b/src/app/index.coffee index 69efcc842c..b8d4a8f71e 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -40,30 +40,25 @@ cleanupCorruptTasks = (model) -> delete tasks[key] true - batch = null + misc.batchTxn model, (uObj, paths) -> + ## Task List Cleanup + ['habit','daily','todo','reward'].forEach (type) -> - ## Task List Cleanup - ['habit','daily','todo','reward'].forEach (type) -> + # 1. remove duplicates + # 2. restore missing zombie tasks back into list + idList = uObj["#{type}Ids"] + taskIds = _.pluck( _.where(tasks, {type:type}), 'id') + union = _.union idList, taskIds - # 1. remove duplicates - # 2. restore missing zombie tasks back into list - idList = user.get("#{type}Ids") - taskIds = _.pluck( _.where(tasks, {type:type}), 'id') - union = _.union idList, taskIds + # 2. remove empty (grey) tasks + preened = _.filter union, (id) -> id and _.contains(taskIds, id) - # 2. remove empty (grey) tasks - preened = _.filter union, (id) -> id and _.contains(taskIds, id) + # There were indeed issues found, set the new list + if !_.isEqual(idList, preened) + uObj["#{type}Ids"] = preened; paths["#{type}Ids"] = true + console.error uObj.id + "'s #{type}s were corrupt." + true - # There were indeed issues found, set the new list - if !_.isEqual(idList, preened) - unless batch? - batch = new require('./character').BatchUpdate(model) - batch.startTransaction() - batch.set("#{type}Ids", preened) - console.error user.get('id') + "'s #{type}s were corrupt." - true - - batch.commit() if batch? ### Subscribe to the user, the users's party (meta info like party name, member ids, etc), and the party's members. 3 subscriptions. @@ -122,6 +117,7 @@ get '/', (page, model, params, next) -> ready (model) -> user = model.at('_user') browser = require './browser' + character = require './character' require('./character').app(exports, model) require('./tasks').app(exports, model) @@ -138,18 +134,14 @@ ready (model) -> ### Cron ### - uObj = misc.hydrate(user.get()) - # habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks - _.each ['habit','daily','todo','reward'], (type) -> - uObj["#{type}s"] = _.where(uObj.tasks, {type:type}); true - paths = {} - algos.cron(uObj, {paths:paths}) - if !_.isEmpty(paths) - if paths['lastCron'] and _.size(paths) is 1 - user.set "lastCron", uObj.lastCron - else - lostHp = delete paths['stats.hp'] # we'll set this manually so we can get a cool animation - _.each paths, (v,k) -> user.pass({cron:true}).set(k,helpers.dotGet(k, uObj)); true - if lostHp + misc.batchTxn model, (uObj, paths) -> + # habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks + _.each ['habit','daily','todo','reward'], (type) -> uObj["#{type}s"] = _.where(uObj.tasks, {type}); true + algos.cron uObj, {paths} + return if _.isEmpty(paths) or (paths['lastCron'] and _.size(paths) is 1) + if lostHp = delete paths['stats.hp'] # we'll set this manually so we can get a cool animation + setTimeout -> browser.resetDom(model) - setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 750 \ No newline at end of file + user.set 'stats.hp', uObj.stats.hp + , 750 + ,{cron:true} \ No newline at end of file diff --git a/src/app/misc.coffee b/src/app/misc.coffee index 735addb804..85e71fe11f 100644 --- a/src/app/misc.coffee +++ b/src/app/misc.coffee @@ -2,6 +2,21 @@ _ = require 'lodash' algos = require 'habitrpg-shared/script/algos' items = require('habitrpg-shared/script/items').items helpers = require('habitrpg-shared/script/helpers') +character = require('./character') + +module.exports.batchTxn = batchTxn = (model, cb, options) -> + user = model.at("_user") + uObj = hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 + paths = {} + model._dontPersist = true + cb uObj, paths + _.each paths, (v,k) -> user.pass({cron:options?.cron}).set(k,helpers.dotGet(k, uObj));true + model._dontPersist = false + # some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js + # pass true if we have levelled to supress xp notification + unless _.isEmpty paths + setOps = _.reduce paths, ((m,v,k)-> m[k] = helpers.dotGet(k,uObj);m), {} + user.set "update__", setOps ### algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the @@ -11,28 +26,24 @@ helpers = require('habitrpg-shared/script/helpers') ### module.exports.score = (model, taskId, direction, allowUndo=false) -> #return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0 - user = model.at("_user") + batchTxn model, (uObj, paths) -> + tObj = uObj.tasks[taskId] - uObj = hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 - tObj = uObj.tasks[taskId] + # Stuff for undo + if allowUndo + tObjBefore = _.cloneDeep tObj + tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo'] + previousUndo = model.get('_undo') + clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId + timeoutId = setTimeout (-> model.del('_undo')), 20000 + model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId} - # Stuff for undo - if allowUndo - tObjBefore = _.cloneDeep tObj - tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo'] - previousUndo = model.get('_undo') - clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId - timeoutId = setTimeout (-> model.del('_undo')), 20000 - model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId} - - paths = {} - delta = algos.score(uObj, tObj, direction, {paths}) - _.each paths, (v,k) -> user.set(k,helpers.dotGet(k, uObj)); true - model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus - if uObj._tmp?.drop and $? - model.set '_drop', uObj._tmp.drop - $('#item-dropped-modal').modal 'show' - delta + delta = algos.score(uObj, tObj, direction, {paths}) + model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus + if uObj._tmp?.drop and $? + model.set '_drop', uObj._tmp.drop + $('#item-dropped-modal').modal 'show' + delta ### Make sure model.get() returns all properties, see https://github.com/codeparty/racer/issues/116 diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index 50b130483d..8e538f2195 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -128,19 +128,16 @@ module.exports.app = (appExports, model) -> appExports.undo = () -> undo = model.get '_undo' clearTimeout(undo.timeoutId) if undo?.timeoutId - batch = character.BatchUpdate(model) - batch.startTransaction() model.del '_undo' - _.each undo.stats, (val, key) -> batch.set "stats.#{key}", val; true + _.each undo.stats, (val, key) -> user.set "stats.#{key}", val; true taskPath = "tasks.#{undo.task.id}" _.each undo.task, (val, key) -> return true if key in ['id', 'type'] # strange bugs in this world: https://workflowy.com/shared/a53582ea-43d6-bcce-c719-e134f9bf71fd/ if key is 'completed' user.pass({cron:true}).set("#{taskPath}.completed",val) else - batch.set "#{taskPath}.#{key}", val + user.set "#{taskPath}.#{key}", val true - batch.commit() appExports.tasksToggleAdvanced = (e, el) -> $(el).next('.advanced-option').toggleClass('visuallyhidden') diff --git a/src/server/private.coffee b/src/server/private.coffee index 65127a51ad..93038a620a 100644 --- a/src/server/private.coffee +++ b/src/server/private.coffee @@ -1,5 +1,5 @@ _ = require 'lodash' -character = require "../app/character" +misc = require "../app/misc" module.exports.middleware = (req, res, next) -> model = req.getModel() @@ -33,12 +33,15 @@ module.exports.app = (appExports, model) -> ### Buy Reroll Button ### - appExports.buyReroll = (e, el, next) -> - batch = new character.BatchUpdate(model) - obj = model.get('_user') - batch.set 'balance', obj.balance-1 - _.each obj.tasks, (task) -> batch.set("tasks.#{task.id}.value", 0) unless task.type is 'reward';true - batch.commit() + appExports.buyReroll = -> + misc.batchTxn model, (uObj, paths) -> + uObj.balance -= 1; paths['balance'] =1 + _.each uObj.tasks, (task) -> + unless task.type is 'reward' + uObj.tasks[task.id].value = 0 + paths["tasks.#{task.id}.value"] = 1 + true + $('#reroll-modal').modal('hide') module.exports.routes = (expressApp) -> ### From 4c684502f2eed5f7b51347b8adf8ca4e7e84ffcf Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 29 May 2013 10:54:16 +0100 Subject: [PATCH 2/2] batch: add convenience batch.set / batch.get for updating paths simultaneously --- src/app/character.coffee | 22 ++++++++-------------- src/app/index.coffee | 4 ++-- src/app/misc.coffee | 5 ++++- src/server/private.coffee | 6 ++---- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/app/character.coffee b/src/app/character.coffee index 08a309e66d..24f0cf477e 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -33,16 +33,11 @@ module.exports.app = (appExports, model) -> items.updateStore(model) appExports.reset = (e, el) -> - misc.batchTxn model, (uObj, paths) -> - taskTypes = ['habit', 'daily', 'todo', 'reward'] - uObj.tasks = {}; paths['tasks'] = true - taskTypes.forEach (type) -> uObj["#{type}Ids"] = []; paths["#{type}Ids"] = true - # Reset stats - [uObj.stats.hp, uObj.stats.lvl, uObj.stats.gp, uObj.stats.exp] = [50, 1, 0, 0] - # Reset items - [uObj.items.armor, uObj.items.weapon, uObj.items.head, uObj.items.shield] = [0, 0, 0, 0] - ['stats.hp', 'stats.lvl', 'stats.gp', 'stats.exp', 'items.armor', 'items.weapon', 'items.head', 'items.shield'].forEach (path) -> - paths[path] = true + misc.batchTxn model, (uObj, paths, batch) -> + batch.set 'tasks', {} + ['habit', 'daily', 'todo', 'reward'].forEach (type) -> batch.set("#{type}Ids", []) + _.each {hp:50, lvl:1, gp:0, exp:0}, (v,k) -> batch.set("stats.#{k}",v) + _.each {armor:0, weapon:0, head:0, shield:0}, (v,k) -> batch.set("items.#{k}",v) items.updateStore(model) browser.resetDom(model) @@ -61,12 +56,11 @@ module.exports.app = (appExports, model) -> appExports.customizeArmorSet = (e, el) -> user.set 'preferences.armorSet', $(el).attr('data-value') - appExports.restoreSave = (e, el) -> - misc.batchTxn model, (uObj, paths) -> + appExports.restoreSave = -> + misc.batchTxn model, (uObj, paths, batch) -> $('#restore-form input').each -> [path, val] = [$(this).attr('data-for'), parseInt($(this).val() || 1)] - helpers.dotSet(path, val, uObj); paths[path] = true - debugger + batch.set(path,val) appExports.toggleHeader = (e, el) -> user.set 'preferences.hideHeader', !user.get('preferences.hideHeader') diff --git a/src/app/index.coffee b/src/app/index.coffee index b8d4a8f71e..e77c8f0bed 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -40,7 +40,7 @@ cleanupCorruptTasks = (model) -> delete tasks[key] true - misc.batchTxn model, (uObj, paths) -> + misc.batchTxn model, (uObj, paths, batch) -> ## Task List Cleanup ['habit','daily','todo','reward'].forEach (type) -> @@ -55,7 +55,7 @@ cleanupCorruptTasks = (model) -> # There were indeed issues found, set the new list if !_.isEqual(idList, preened) - uObj["#{type}Ids"] = preened; paths["#{type}Ids"] = true + batch.set("#{type}Ids", preened) console.error uObj.id + "'s #{type}s were corrupt." true diff --git a/src/app/misc.coffee b/src/app/misc.coffee index 85e71fe11f..369946bed5 100644 --- a/src/app/misc.coffee +++ b/src/app/misc.coffee @@ -7,9 +7,12 @@ character = require('./character') module.exports.batchTxn = batchTxn = (model, cb, options) -> user = model.at("_user") uObj = hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 + batch = + set: (k,v) -> helpers.dotSet(k,v,uObj); paths[k] = true + get: (k) -> helpers.dotGet(k,uObj) paths = {} model._dontPersist = true - cb uObj, paths + cb uObj, paths, batch _.each paths, (v,k) -> user.pass({cron:options?.cron}).set(k,helpers.dotGet(k, uObj));true model._dontPersist = false # some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js diff --git a/src/server/private.coffee b/src/server/private.coffee index 93038a620a..ab72c8206d 100644 --- a/src/server/private.coffee +++ b/src/server/private.coffee @@ -34,12 +34,10 @@ module.exports.app = (appExports, model) -> Buy Reroll Button ### appExports.buyReroll = -> - misc.batchTxn model, (uObj, paths) -> + misc.batchTxn model, (uObj, paths, batch) -> uObj.balance -= 1; paths['balance'] =1 _.each uObj.tasks, (task) -> - unless task.type is 'reward' - uObj.tasks[task.id].value = 0 - paths["tasks.#{task.id}.value"] = 1 + batch.set("tasks.#{task.id}.value", 0) unless task.type is 'reward' true $('#reroll-modal').modal('hide')