Significantly reduced memory usage and simulation time by further limiting the amount of Thangs which even start tracking ThangState in the first place.

This commit is contained in:
Nick Winter 2014-11-17 21:30:44 -08:00
parent 95dca575d1
commit bf71893ddf
5 changed files with 47 additions and 19 deletions

View file

@ -392,20 +392,29 @@ self.onWorldLoaded = function onWorldLoaded() {
self.postMessage({type: 'end-load-frames', goalStates: goalStates, overallStatus: overallStatus});
var t1 = new Date();
var diff = t1 - self.t0;
if (self.world.headless)
if(self.world.headless)
return console.log('Headless simulation completed in ' + diff + 'ms.');
var worldEnded = self.world.ended;
var totalFrames = self.world.totalFrames;
var transferableSupported = self.transferableSupported();
try {
var serialized = self.world.serialize();
}
catch(error) {
console.log("World serialization error:", error.toString() + "\n" + error.stack || error.stackTrace);
self.destroyWorld();
return;
}
//self.serialized = serialized; // Testing peak memory usage
//return; // Testing peak memory usage
if(worldEnded)
// Make sure we clean up memory as soon as possible, since we just used the most ever and don't want to crash.
self.destroyWorld();
var t2 = new Date();
//console.log("About to transfer", serialized.serializedWorld.trackedPropertiesPerThangValues, serialized.transferableObjects);
var messageType = self.world.ended ? 'new-world' : 'some-frames-serialized';
var messageType = worldEnded ? 'new-world' : 'some-frames-serialized';
try {
var message = {type: messageType, serialized: serialized.serializedWorld, goalStates: goalStates, startFrame: serialized.startFrame, endFrame: serialized.endFrame};
if(transferableSupported)
@ -417,15 +426,18 @@ self.onWorldLoaded = function onWorldLoaded() {
console.log("World delivery error:", error.toString() + "\n" + error.stack || error.stackTrace);
}
if(self.world.ended) {
if(worldEnded) {
var t3 = new Date();
console.log("And it was so: (" + (diff / self.world.totalFrames).toFixed(3) + "ms per frame,", self.world.totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms");
self.world.goalManager.destroy();
self.world.destroy();
self.world = null;
console.log("And it was so: (" + (diff / totalFrames).toFixed(3) + "ms per frame,", totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms");
}
};
self.destroyWorld = function destroyWorld() {
self.world.goalManager.destroy();
self.world.destroy();
self.world = null;
};
self.onWorldPreloaded = function onWorldPreloaded() {
self.goalManager.worldGenerationEnded();
var goalStates = self.goalManager.getGoalStates();

View file

@ -17,6 +17,7 @@ REAL_TIME_BUFFER_MAX = 3 * PROGRESS_UPDATE_INTERVAL
REAL_TIME_BUFFERED_WAIT_INTERVAL = 0.5 * PROGRESS_UPDATE_INTERVAL
REAL_TIME_COUNTDOWN_DELAY = 3000 # match CountdownScreen
ITEM_ORIGINAL = '53e12043b82921000051cdf9'
EXISTS_ORIGINAL = '524b4150ff92f1f4f8000024'
COUNTDOWN_LEVELS = ['sky-span']
module.exports = class World
@ -237,13 +238,19 @@ module.exports = class World
loadThangFromLevel: (thangConfig, levelComponents, thangTypes, equipBy=null) ->
components = []
for component in thangConfig.components
for component, componentIndex in thangConfig.components
componentModel = _.find levelComponents, (c) ->
c.original is component.original and c.version.major is (component.majorVersion ? 0)
componentClass = @loadClassFromCode componentModel.js, componentModel.name, 'component'
components.push [componentClass, component.config]
if equipBy and component.original is ITEM_ORIGINAL
component.config.ownerID = equipBy
if component.original is ITEM_ORIGINAL
isItem = true
component.config.ownerID = equipBy if equipBy
else if component.original is EXISTS_ORIGINAL
existsConfigIndex = componentIndex
if isItem and existsConfigIndex?
# For memory usage performance, make sure these don't get any tracked properties assigned.
components[existsConfigIndex][1] = {exists: false, stateless: true}
thangTypeOriginal = thangConfig.thangType
thangTypeModel = _.find thangTypes, (t) -> t.original is thangTypeOriginal
return console.error thangConfig.id ? equipBy, 'could not find ThangType for', thangTypeOriginal unless thangTypeModel
@ -347,6 +354,7 @@ module.exports = class World
serialize: ->
# Code hotspot; optimize it
@freeMemoryBeforeFinalSerialization() if @ended
startFrame = @framesSerializedSoFar
endFrame = @frames.length
#console.log "... world serializing frames from", startFrame, "to", endFrame, "of", @totalFrames
@ -437,6 +445,7 @@ module.exports = class World
o.scriptNotes = (sn.serialize() for sn in @scriptNotes)
if o.scriptNotes.length > 200
console.log 'Whoa, serializing a lot of WorldScriptNotes here:', o.scriptNotes.length
@freeMemoryAfterEachSerialization() unless @ended
{serializedWorld: o, transferableObjects: [o.storageBuffer], startFrame: startFrame, endFrame: endFrame}
@deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback, startFrame, endFrame, level, streamingWorld) ->
@ -535,6 +544,13 @@ module.exports = class World
console.log 'No frames were changed out of all', @frames.length
firstChangedFrame
freeMemoryBeforeFinalSerialization: ->
@levelComponents = null
@thangTypes = null
freeMemoryAfterEachSerialization: ->
@frames[i] = null for frame, i in @frames when i < @frames.length - 1
pointsForThang: (thangID, frameStart=0, frameEnd=null, camera=null, resolution=4) ->
# Optimized
@pointsForThangCache ?= {}

View file

@ -17,7 +17,7 @@ module.exports = class WorldFrame
return nextFrame
setState: ->
for thang in @world.thangs
for thang in @world.thangs when not thang.stateless
@thangStateMap[thang.id] = thang.getState()
restoreState: ->

View file

@ -97,7 +97,7 @@ module.exports = class PlayLevelView extends RootView
@isEditorPreview = @getQueryVariable 'dev'
@sessionID = @getQueryVariable 'session'
@opponentSessionID = @getQueryVariable('opponent')
@opponentSessionID ?= @options.opponent
@ -369,7 +369,7 @@ module.exports = class PlayLevelView extends RootView
return if @alreadyLoadedState
@alreadyLoadedState = true
state = @originalSessionState
if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
if not @level or @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: true
Backbone.Mediator.publish 'tome:select-primary-sprite', {}
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: false
@ -552,7 +552,7 @@ module.exports = class PlayLevelView extends RootView
return if @destroyed
# TODO: Show a victory dialog specific to hero-ladder level
if @goalManager.checkOverallStatus() is 'success' and not @options.realTimeMultiplayerSessionID?
Backbone.Mediator.publish 'level:show-victory', showModal: true
Backbone.Mediator.publish 'level:show-victory', showModal: true
destroy: ->
@levelLoader?.destroy()
@ -753,7 +753,7 @@ module.exports = class PlayLevelView extends RootView
if @realTimeOpponent.get('state') is 'ready'
@realTimeOpponent.off 'change', @realTimeOpponentMaybeReady
@realTimeOpponentIsReady()
realTimeOpponentIsReady: =>
console.info 'All real-time multiplayer players are ready!'
@realTimeSession.set 'state', 'running'
@ -840,7 +840,7 @@ module.exports = class PlayLevelView extends RootView
# TODO: This isn't always getting updated where the random seed generation uses it.
sessionState.submissionCount = parseInt newSubmissionCount
console.info 'Got multiplayer submissionCount', sessionState.submissionCount
@session.set 'state', sessionState
@session.set 'state', sessionState
@session.patch()
# Reload this level so the opponent session can easily be wired up
@ -862,7 +862,7 @@ module.exports = class PlayLevelView extends RootView
return if me.id is @realTimeSession.get('creator')
oldTeam = @realTimeOpponent.get('team')
return unless oldTeam is @session.get('team')
return unless oldTeam is @session.get('team')
# Need to switch to other team
newTeam = if oldTeam is 'humans' then 'ogres' else 'humans'
@ -898,7 +898,7 @@ module.exports = class PlayLevelView extends RootView
if sessionState?
# TODO: Don't hard code thangID
sessionState.selected = if newTeam is 'humans' then 'Hero Placeholder' else 'Hero Placeholder 1'
@session.set 'state', sessionState
@session.set 'state', sessionState
@session.set 'code', code
@session.patch()

View file

@ -124,7 +124,7 @@ module.exports.getTwoGames = (req, res) ->
#if userIsAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.')
humansGameID = req.body.humansGameID
ogresGameID = req.body.ogresGameID
ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span', 'dueling-grounds', 'cavern-survival', 'intuit-tf2014']
ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span', 'dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove']
levelID = _.sample ladderGameIDs
unless ogresGameID and humansGameID
#fetch random games here