mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-02 16:21:01 -04:00
Merge pull request #894 from codecombat/feature/worker-lint
Background linting/transpilation/hasChangedSignificantly
This commit is contained in:
commit
7419b269f1
7 changed files with 1232 additions and 60 deletions
99
app/assets/javascripts/workers/aether_worker.js
Normal file
99
app/assets/javascripts/workers/aether_worker.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
var window = self;
|
||||
var Global = self;
|
||||
|
||||
importScripts("/javascripts/tome_aether.js");
|
||||
console.log("imported scripts!");
|
||||
var aethers = {};
|
||||
|
||||
var createAether = function (spellKey, options)
|
||||
{
|
||||
aethers[spellKey] = new Aether(options);
|
||||
return JSON.stringify({
|
||||
"message": "Created aether for " + spellKey,
|
||||
"function": "createAether"
|
||||
});
|
||||
};
|
||||
|
||||
var hasChangedSignificantly = function(spellKey, a,b,careAboutLineNumbers,careAboutLint) {
|
||||
|
||||
var hasChanged = aethers[spellKey].hasChangedSignificantly(a,b,careAboutLineNumbers,careAboutLint);
|
||||
var functionName = "hasChangedSignificantly";
|
||||
var returnObject = {
|
||||
"function":functionName,
|
||||
"hasChanged": hasChanged,
|
||||
"spellKey": spellKey
|
||||
};
|
||||
return JSON.stringify(returnObject);
|
||||
};
|
||||
|
||||
var updateLanguageAether = function(newLanguage)
|
||||
{
|
||||
for (var spellKey in aethers)
|
||||
{
|
||||
if (aethers.hasOwnProperty(spellKey))
|
||||
{
|
||||
aethers[spellKey].setLanguage(newLanguage);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
var lint = function(spellKey, source)
|
||||
{
|
||||
var currentAether = aethers[spellKey];
|
||||
var lintMessages = currentAether.lint(source);
|
||||
var functionName = "lint";
|
||||
var returnObject = {
|
||||
"lintMessages": lintMessages,
|
||||
"function": functionName
|
||||
};
|
||||
return JSON.stringify(returnObject);
|
||||
};
|
||||
|
||||
var transpile = function(spellKey, source)
|
||||
{
|
||||
var currentAether = aethers[spellKey];
|
||||
currentAether.transpile(source);
|
||||
var functionName = "transpile";
|
||||
var returnObject = {
|
||||
"problems": currentAether.problems,
|
||||
"function": functionName,
|
||||
"spellKey": spellKey
|
||||
};
|
||||
return JSON.stringify(returnObject);
|
||||
};
|
||||
self.addEventListener('message', function(e) {
|
||||
var data = JSON.parse(e.data);
|
||||
if (data.function == "createAether")
|
||||
{
|
||||
self.postMessage(createAether(data.spellKey, data.options));
|
||||
}
|
||||
else if (data.function == "updateLanguageAether")
|
||||
{
|
||||
updateLanguageAether(data.newLanguage)
|
||||
}
|
||||
else if (data.function == "hasChangedSignificantly")
|
||||
{
|
||||
self.postMessage(hasChangedSignificantly(
|
||||
data.spellKey,
|
||||
data.a,
|
||||
data.b,
|
||||
data.careAboutLineNumbers,
|
||||
data.careAboutLint
|
||||
));
|
||||
}
|
||||
else if (data.function == "lint")
|
||||
{
|
||||
self.postMessage(lint(data.spellKey, data.source));
|
||||
}
|
||||
else if (data.function == "transpile")
|
||||
{
|
||||
self.postMessage(transpile(data.spellKey, data.source));
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = "Didn't execute any function...";
|
||||
var returnObject = {"message":message, "function":"none"};
|
||||
self.postMessage(JSON.stringify(returnObject));
|
||||
}
|
||||
}, false);
|
|
@ -74,16 +74,20 @@ module.exports = class CastButtonView extends View
|
|||
|
||||
updateCastButton: ->
|
||||
return if _.some @spells, (spell) => not spell.loaded
|
||||
castable = _.some @spells, (spell) => spell.hasChangedSignificantly spell.getSource()
|
||||
@castButtonGroup.toggleClass('castable', castable).toggleClass('casting', @casting)
|
||||
if @casting
|
||||
s = $.i18n.t("play_level.tome_cast_button_casting", defaultValue: "Casting")
|
||||
else if castable
|
||||
s = $.i18n.t("play_level.tome_cast_button_castable", defaultValue: "Cast Spell") + " " + @castShortcut
|
||||
else
|
||||
s = $.i18n.t("play_level.tome_cast_button_cast", defaultValue: "Spell Cast")
|
||||
@castButton.text s
|
||||
@castButton.prop 'disabled', not castable
|
||||
|
||||
async.some _.values(@spells), (spell, callback) =>
|
||||
spell.hasChangedSignificantly spell.getSource(), null, callback
|
||||
, (castable) =>
|
||||
|
||||
@castButtonGroup.toggleClass('castable', castable).toggleClass('casting', @casting)
|
||||
if @casting
|
||||
s = $.i18n.t("play_level.tome_cast_button_casting", defaultValue: "Casting")
|
||||
else if castable
|
||||
s = $.i18n.t("play_level.tome_cast_button_castable", defaultValue: "Cast Spell") + " " + @castShortcut
|
||||
else
|
||||
s = $.i18n.t("play_level.tome_cast_button_cast", defaultValue: "Spell Cast")
|
||||
@castButton.text s
|
||||
@castButton.prop 'disabled', not castable
|
||||
|
||||
setAutocastDelay: (delay) ->
|
||||
#console.log "Set autocast delay to", delay
|
||||
|
|
|
@ -23,7 +23,7 @@ module.exports = class Spell
|
|||
@parameters = p.parameters
|
||||
@permissions = read: p.permissions?.read ? [], readwrite: p.permissions?.readwrite ? [] # teams
|
||||
@thangs = {}
|
||||
@view = new SpellView {spell: @, session: @session}
|
||||
@view = new SpellView {spell: @, session: @session, worker: @worker}
|
||||
@view.render() # Get it ready and code loaded in advance
|
||||
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel
|
||||
@tabView.render()
|
||||
|
@ -75,15 +75,27 @@ module.exports = class Spell
|
|||
hasChanged: (newSource=null, currentSource=null) ->
|
||||
(newSource ? @originalSource) isnt (currentSource ? @source)
|
||||
|
||||
hasChangedSignificantly: (newSource=null, currentSource=null) ->
|
||||
hasChangedSignificantly: (newSource=null, currentSource=null, cb) ->
|
||||
for thangID, spellThang of @thangs
|
||||
aether = spellThang.aether
|
||||
break
|
||||
unless aether
|
||||
console.error @toString(), "couldn't find a spellThang with aether of", @thangs
|
||||
return false
|
||||
aether.hasChangedSignificantly (newSource ? @originalSource), (currentSource ? @source), true, true
|
||||
|
||||
cb false
|
||||
workerMessage =
|
||||
function: "hasChangedSignificantly"
|
||||
a: (newSource ? @originalSource)
|
||||
spellKey: @spellKey
|
||||
b: (currentSource ? @source)
|
||||
careAboutLineNumbers: true
|
||||
careAboutLint: true
|
||||
@worker.addEventListener "message", (e) =>
|
||||
workerData = JSON.parse e.data
|
||||
if workerData.function is "hasChangedSignificantly" and workerData.spellKey is @spellKey
|
||||
@worker.removeEventListener("message",arguments.callee, false)
|
||||
cb(workerData.hasChanged)
|
||||
@worker.postMessage JSON.stringify(workerMessage)
|
||||
|
||||
createAether: (thang) ->
|
||||
aceConfig = me.get('aceConfig') ? {}
|
||||
aetherOptions =
|
||||
|
@ -111,13 +123,23 @@ module.exports = class Spell
|
|||
aetherOptions.includeFlow = false
|
||||
#console.log "creating aether with options", aetherOptions
|
||||
aether = new Aether aetherOptions
|
||||
workerMessage =
|
||||
function: "createAether"
|
||||
spellKey: @spellKey
|
||||
options: aetherOptions
|
||||
@worker.postMessage JSON.stringify workerMessage
|
||||
aether
|
||||
|
||||
updateLanguageAether: ->
|
||||
aceConfig = me.get('aceConfig') ? {}
|
||||
newLanguage = (aceConfig.language ? 'javascript')
|
||||
for thangId, spellThang of @thangs
|
||||
spellThang.aether?.setLanguage (aceConfig.language ? 'javascript')
|
||||
spellThang.aether?.setLanguage newLanguage
|
||||
spellThang.castAether = null
|
||||
workerMessage =
|
||||
function: "updateLanguageAether"
|
||||
newLanguage: newLanguage
|
||||
@worker.postMessage JSON.stringify workerMessage
|
||||
@transpile()
|
||||
|
||||
toString: ->
|
||||
|
|
|
@ -47,6 +47,7 @@ module.exports = class SpellView extends View
|
|||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@worker = options.worker
|
||||
@session = options.session
|
||||
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
|
||||
@spell = options.spell
|
||||
|
@ -278,9 +279,10 @@ module.exports = class SpellView extends View
|
|||
]
|
||||
@onCodeChangeMetaHandler = =>
|
||||
return if @eventsSuppressed
|
||||
if not @spellThang or @spell.hasChangedSignificantly @getSource(), @spellThang.aether.raw
|
||||
callback() for callback in onSignificantChange # Do these first
|
||||
callback() for callback in onAnyChange # Then these
|
||||
@spell.hasChangedSignificantly @getSource(), @spellThang.aether.raw, (hasChanged) =>
|
||||
if not @spellThang or hasChanged
|
||||
callback() for callback in onSignificantChange # Do these first
|
||||
callback() for callback in onAnyChange # Then these
|
||||
@aceDoc.on 'change', @onCodeChangeMetaHandler
|
||||
|
||||
setRecompileNeeded: (needed=true) =>
|
||||
|
@ -317,20 +319,37 @@ module.exports = class SpellView extends View
|
|||
# to a new spellThang, we may want to refresh our Aether display.
|
||||
return unless aether = @spellThang?.aether
|
||||
source = @getSource()
|
||||
codeHasChangedSignificantly = force or @spell.hasChangedSignificantly source, aether.raw
|
||||
needsUpdate = codeHasChangedSignificantly or @spellThang isnt @lastUpdatedAetherSpellThang
|
||||
return if not needsUpdate and aether is @displayedAether
|
||||
castAether = @spellThang.castAether
|
||||
codeIsAsCast = castAether and not @spell.hasChangedSignificantly source, castAether.raw
|
||||
aether = castAether if codeIsAsCast
|
||||
return if not needsUpdate and aether is @displayedAether
|
||||
|
||||
# Now that that's figured out, perform the update.
|
||||
@clearAetherDisplay()
|
||||
aether.transpile source if codeHasChangedSignificantly and not codeIsAsCast
|
||||
@displayAether aether
|
||||
@lastUpdatedAetherSpellThang = @spellThang
|
||||
@guessWhetherFinished aether if fromCodeChange
|
||||
@spell.hasChangedSignificantly source, aether.raw, (hasChanged) =>
|
||||
codeHasChangedSignificantly = force or hasChanged
|
||||
needsUpdate = codeHasChangedSignificantly or @spellThang isnt @lastUpdatedAetherSpellThang
|
||||
return if not needsUpdate and aether is @displayedAether
|
||||
castAether = @spellThang.castAether
|
||||
codeIsAsCast = castAether and not hasChanged
|
||||
aether = castAether if codeIsAsCast
|
||||
return if not needsUpdate and aether is @displayedAether
|
||||
|
||||
# Now that that's figured out, perform the update.
|
||||
# The web worker Aether won't track state, so don't have to worry about updating it
|
||||
@clearAetherDisplay()
|
||||
workerMessage =
|
||||
function: "transpile"
|
||||
spellKey: @spell.spellKey
|
||||
source: source
|
||||
|
||||
@worker.addEventListener "message", (e) =>
|
||||
workerData = JSON.parse e.data
|
||||
if workerData.function is "transpile" and workerData.spellKey is @spell.spellKey
|
||||
@worker.removeEventListener("message",arguments.callee, false)
|
||||
aether.problems = workerData.problems
|
||||
aether.raw = source
|
||||
@displayAether aether
|
||||
@lastUpdatedAetherSpellThang = @spellThang
|
||||
@guessWhetherFinished aether if fromCodeChange
|
||||
@worker.postMessage JSON.stringify(workerMessage)
|
||||
#aether.transpile source if codeHasChangedSignificantly and not codeIsAsCast
|
||||
#@displayAether aether
|
||||
#@lastUpdatedAetherSpellThang = @spellThang
|
||||
#@guessWhetherFinished aether if fromCodeChange
|
||||
|
||||
clearAetherDisplay: ->
|
||||
problem.destroy() for problem in @problems
|
||||
|
@ -400,10 +419,11 @@ module.exports = class SpellView extends View
|
|||
return @onInfiniteLoop e if e.problem.id is "runtime_InfiniteLoop"
|
||||
return unless e.problem.userInfo.methodName is @spell.name
|
||||
return unless spellThang = _.find @spell.thangs, (spellThang, thangID) -> thangID is e.problem.userInfo.thangID
|
||||
return if @spell.hasChangedSignificantly @getSource() # don't show this error if we've since edited the code
|
||||
spellThang.aether.addProblem e.problem
|
||||
@lastUpdatedAetherSpellThang = null # force a refresh without a re-transpile
|
||||
@updateAether false, false
|
||||
@spell.hasChangedSignificantly @getSource(), null, (hasChanged) =>
|
||||
return if hasChanged
|
||||
spellThang.aether.addProblem e.problem
|
||||
@lastUpdatedAetherSpellThang = null # force a refresh without a re-transpile
|
||||
@updateAether false, false
|
||||
|
||||
onInfiniteLoop: (e) ->
|
||||
return unless @spellThang
|
||||
|
@ -578,7 +598,9 @@ module.exports = class SpellView extends View
|
|||
@aceSession.setMode @editModes[aceConfig.language ? 'javascript']
|
||||
|
||||
dismiss: ->
|
||||
@recompile() if @spell.hasChangedSignificantly @getSource()
|
||||
@spell.hasChangedSignificantly @getSource(), null, (hasChanged) =>
|
||||
@recompile() if hasChanged
|
||||
|
||||
|
||||
destroy: ->
|
||||
$(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
|
||||
|
|
|
@ -71,7 +71,7 @@ module.exports = class TomeView extends View
|
|||
@cast()
|
||||
console.warn "Warning: There are no Programmable Thangs in this level, which makes it unplayable."
|
||||
delete @options.thangs
|
||||
|
||||
|
||||
onNewWorld: (e) ->
|
||||
thangs = _.filter e.world.thangs, 'isSelectable'
|
||||
programmableThangs = _.filter thangs, 'isProgrammable'
|
||||
|
@ -88,26 +88,7 @@ module.exports = class TomeView extends View
|
|||
@cast()
|
||||
|
||||
createWorker: ->
|
||||
return
|
||||
# In progress
|
||||
worker = cw
|
||||
initialize: (scope) ->
|
||||
self.window = self
|
||||
self.global = self
|
||||
console.log 'Tome worker initialized.'
|
||||
doIt: (data, callback, scope) ->
|
||||
console.log 'doing', what
|
||||
try
|
||||
importScripts '/javascripts/tome_aether.js'
|
||||
catch err
|
||||
console.log err.toString()
|
||||
a = new Aether()
|
||||
callback 'good'
|
||||
undefined
|
||||
onAccepted = (s) -> console.log 'accepted', s
|
||||
onRejected = (s) -> console.log 'rejected', s
|
||||
worker.doIt('hmm').then onAccepted, onRejected
|
||||
worker
|
||||
return new Worker("/javascripts/workers/aether_worker.js")
|
||||
|
||||
generateTeamSpellMap: (spellObject) ->
|
||||
teamSpellMap = {}
|
||||
|
@ -230,5 +211,5 @@ module.exports = class TomeView extends View
|
|||
|
||||
destroy: ->
|
||||
spell.destroy() for spellKey, spell of @spells
|
||||
@worker?._close()
|
||||
@worker?.terminate()
|
||||
super()
|
||||
|
|
|
@ -59,6 +59,7 @@ exports.config =
|
|||
# Aether before box2d for some strange Object.defineProperty thing
|
||||
'bower_components/aether/build/aether.js'
|
||||
'bower_components/d3/d3.min.js'
|
||||
'vendor/scripts/async.js'
|
||||
]
|
||||
stylesheets:
|
||||
defaultExtension: 'sass'
|
||||
|
|
1043
vendor/scripts/async.js
vendored
Normal file
1043
vendor/scripts/async.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue