Changeset - 29a1382de5c8
[Not reviewed]
default
0 5 0
Drew Perttula - 9 years ago 2016-06-09 06:34:28
drewp@bigasterisk.com
introduce autodep handler tree; only rerun highest-level affected handler and let it rerun its children
Ignore-this: dfcc81bbab0d02c40cd79be86c1105d4
5 files changed with 30 insertions and 20 deletions:
0 comments (0 inline, 0 general)
light9/web/edit-choice.coffee
Show inline comments
 
@@ -47,20 +47,20 @@ Polymer
 
        label: {type: String, notify: true}
 

	
 
    observers: [
 
      'gotGraph(graph, uri)'
 
      ]
 

	
 
    ready: ->
 
      setupDrop @$.box, @$.box, null, (uri) =>
 
        @uri=uri
 
        @updateLabel()
 

	
 
    gotGraph: ->
 
      @graph.runHandler(@updateLabel.bind(@))
 
      @graph.runHandler(@updateLabel.bind(@), "edit-choice #{@uri}")
 
        
 
    updateLabel: ->
 
      @label = try
 
          @graph.stringValue(@uri, RDFS + 'label')
 
        catch
 
          @uri
 

	
light9/web/graph.coffee
Show inline comments
 
@@ -50,63 +50,69 @@ class GraphWatchers # throw this one awa
 
  graphChanged: (patch) ->
 
    for quad in patch.delQuads
 
      for cb in @matchingHandlers(quad)
 
        # currently calls multiple times, which is ok, but we might
 
        # group things into fewer patches
 
        cb({delQuads: [quad], addQuads: []})
 
    for quad in patch.addQuads
 
      for cb in @matchingHandlers(quad)
 
        cb({delQuads: [], addQuads: [quad]})
 

	
 
class Handler
 
  # a function and the quad patterns it cared about
 
  constructor: (@func) ->
 
    patterns = [] # s,p,o,g quads that should trigger the next run
 
  constructor: (@func, @label) ->
 
    @patterns = [] # s,p,o,g quads that should trigger the next run
 
    @innerHandlers = [] # Handlers requested while this one was running
 
  
 
class AutoDependencies
 
  constructor: () ->
 
    @handlers = [] # all known Handlers (at least those with non-empty patterns)
 
    @handlerStack = [] # currently running
 
    @handlers = new Handler(null) # tree of all known Handlers (at least those with non-empty patterns). Top node is not a handler.
 
    @handlerStack = [@handlers] # currently running
 
    
 
  runHandler: (func) ->
 
  runHandler: (func, label) ->
 
    # what if we have this func already? duplicate is safe?
 
    
 
    h = new Handler(func)
 
    @handlers.push(h)
 

	
 
    h = new Handler(func, label)
 
    @handlerStack[@handlerStack.length - 1].innerHandlers.push(h)
 
    @_rerunHandler(h)
 
    
 
  _rerunHandler: (handler) ->
 
    handler.patterns = []
 
    @handlerStack.push(handler)
 
    try
 
      handler.func()
 
    catch e
 
      log('error running handler: ', e)
 
      # assuming here it didn't get to do all its queries, we could
 
      # add a *,*,*,* handler to call for sure the next time?
 
    finally
 
      #log('done. got: ', handler.patterns)
 
      @handlerStack.pop()
 
    # handler might have no watches, in which case we could forget about it
 
    
 
  graphChanged: (patch) ->
 
    # SyncedGraph is telling us this patch just got applied to the graph.
 
    for h in @handlers
 
      @_rerunHandler(h)
 

	
 
    rerunInners = (cur) =>
 
      toRun = cur.innerHandlers.slice()
 
      for child in toRun
 
        child.innerHandlers = [] # let all children get called again
 
        @_rerunHandler(child)
 
    rerunInners(@handlers)
 

	
 
  askedFor: (s, p, o, g) ->
 
    # SyncedGraph is telling us someone did a query that depended on
 
    # quads in the given pattern.
 
    current = @handlerStack[@handlerStack.length - 1]
 
    if current?
 
    if current? and current != @handlers
 
      current.patterns.push([s, p, o, g])
 
      #log('push', s,p,o,g)
 

	
 
class window.SyncedGraph
 
  # Main graph object for a browser to use. Syncs both ways with
 
  # rdfdb. Meant to hide the choice of RDF lib, so we can change it
 
  # later.
 
  # 
 
  # Note that _applyPatch is the only method to write to the graph, so
 
  # it can fire subscriptions.
 

	
 
  constructor: (@patchSenderUrl, @prefixes, @setStatus) ->
 
@@ -184,41 +190,42 @@ class window.SyncedGraph
 
    # adds (s,p,newObject,c). Values in other graphs are not affected.
 
    existing = @graph.findByIRI(s, p, null, g)
 
    return {
 
      delQuads: existing,
 
      addQuads: [{subject: s, predicate: p, object: newObject, graph: g}]
 
    }
 

	
 
  patchObject: (s, p, newObject, g) ->
 
    @applyAndSendPatch(@getObjectPatch(s, p, newObject, g))
 
  
 

	
 
  subscribe: (s, p, o, onChange) -> # return subscription handle
 
    throw
 
    # onChange is called with a patch that's limited to the quads
 
    # that match your request.
 
    # We call you immediately on existing triples.
 
    handle = @_watchers.subscribe(s, p, o, onChange)
 
    immediatePatch = {delQuads: [], addQuads: @graph.findByIRI(s, p, o)}
 
    if immediatePatch.addQuads.length
 
      onChange(immediatePatch)
 
    return handle
 

	
 
  unsubscribe: (subscription) ->
 
    @_watchers.unsubscribe(subscription)
 

	
 
  runHandler: (func) ->
 
  runHandler: (func, label) ->
 
    # runs your func once, tracking graph calls. if a future patch
 
    # matches what you queried, we runHandler your func again (and
 
    # forget your queries from the first time).
 
    @_autoDeps.runHandler(func)
 
    @_autoDeps.runHandler(func, label)
 

	
 
  _singleValue: (s, p) ->
 
    @_autoDeps.askedFor(s, p, null, null)
 
    quads = @graph.findByIRI(s, p)
 
    switch quads.length
 
      when 0
 
        throw new Error("no value for "+s+" "+p)
 
      when 1
 
        obj = quads[0].object
 
        return obj
 
      else
 
        throw new Error("too many values: " + JSON.stringify(quads))
light9/web/timeline/adjustable.coffee
Show inline comments
 
@@ -84,39 +84,40 @@ class window.AdjustableFloatObservable e
 

	
 
class window.AdjustableFloatObject extends Adjustable
 
  constructor: (@config) ->
 
    # config also has:
 
    #   graph
 
    #   subj
 
    #   pred
 
    #   ctx
 
    #   getTargetTransform(value) -> getTarget result for value
 
    #   getValueForPos
 

	
 
    super(@config)
 
    @config.graph.runHandler(@_syncValue.bind(@))
 
    @config.graph.runHandler(@_syncValue.bind(@), "adj sync #{@config.subj}")
 

	
 
  _syncValue: () ->
 
    @_currentValue = @config.graph.floatValue(@config.subj, @config.pred)
 
    @_onChange() if @_onChange
 
    
 
  _getValue: () ->
 
    # this is a big speedup- callers use _getValue about 4x as much as
 
    # the graph changes and graph.floatValue is slow
 
    @_currentValue
 

	
 
  getTarget: () ->
 
    @config.getTargetTransform(@_getValue())
 
    
 
  subscribe: (onChange) ->
 
    # only works on one subscription at a time
 
    throw new Error('multi subscribe not implemented') if @_onChange
 
    @_onChange = onChange
 

	
 
    
 
  continueDrag: (pos) ->
 
    # pos is vec2 of pixels relative to the drag start
 
    
 
    newValue = @config.getValueForPos(@_editorCoordinates())
 
    
 
    @config.graph.patchObject(@config.subj, @config.pred,
 
                              @config.graph.LiteralRoundedFloat(newValue),
 
                              @config.ctx)
light9/web/timeline/light9-timeline-audio.html
Show inline comments
 
@@ -53,25 +53,25 @@
 
               } catch(e) {
 
                   return;
 
               }
 
               
 
               try {
 
                   var filename = this.graph.stringValue(
 
                       this.song, this.graph.Uri(':songFilename'));
 
               } catch(e) {
 
                   return;
 
               }
 
               
 
               this.imgSrc = root + '/' + filename.replace('.wav', '.png');
 
           }.bind(this));
 
           }.bind(this), "timeline-audio " + this.song);
 
       },
 
       _imgWidth: function(zoom) {
 
           if (!zoom.duration) {
 
               return "100%";
 
           }
 

	
 
           return (100 / ((zoom.t2 - zoom.t1) / zoom.duration)) + "%";
 
       },
 
       _imgLeft: function(zoom) {
 
           if (!zoom.duration) {
 
               return "0";
 
           }
light9/web/timeline/timeline.coffee
Show inline comments
 
@@ -283,25 +283,25 @@ Polymer
 
  properties:
 
    graph: { type: Object, notify: true }
 
    dia: { type: Object, notify: true }
 
    song:  { type: String, notify: true }
 
    zoomInX: { type: Object, notify: true }
 
    noteUris: { type: Array, notify: true }
 
    rowIndex: { type: Object, notify: true }
 
  observers: [
 
    'onGraph(graph)'
 
    'update(song)'
 
    ]
 
  onGraph: ->
 
    @graph.runHandler(@update.bind(@))
 
    @graph.runHandler(@update.bind(@), "row notes #{@rowIndex}")
 
  update: ->
 
    U = (x) -> @graph.Uri(x)
 

	
 
    @noteUris = []
 
    for note in @graph.objects(@song, U(':note'))
 
      @push('noteUris', note)
 

	
 
Polymer
 
  is: 'light9-timeline-note'
 
  behaviors: [ Polymer.IronResizableBehavior ]
 
  listeners: 'iron-resize': 'update'
 
  properties:
 
@@ -310,25 +310,25 @@ Polymer
 
    uri: { type: String, notify: true }
 
    zoomInX: { type: Object, notify: true }
 
  observers: [
 
    'onUri(graph, dia, uri)'
 
    'update(graph, dia, uri, zoomInX)'
 
    ]
 
  ready: ->
 

	
 
  detached: ->
 
    @dia.clearElem(@uri)
 

	
 
  onUri: ->
 
    @graph.runHandler(@update.bind(@))
 
    @graph.runHandler(@update.bind(@), "note updates #{@uri}")
 
    
 
  update: ->
 
    # update our note DOM and SVG elements based on the graph
 
    U = (x) -> @graph.Uri(x)
 
    try
 
      worldPts = [] # (song time, value)
 

	
 
      originTime = @graph.floatValue(@uri, U(':originTime'))
 
      for curve in @graph.objects(@uri, U(':curve'))
 
        if @graph.uriValue(curve, U(':attr')) == U(':strength')
 
          
 
          for pt in @graph.objects(curve, U(':point'))
 
@@ -352,25 +352,25 @@ Polymer
 
  properties:
 
    adjs: { type: Array }, # our computed list
 
    parentAdjs: { type: Array }, # incoming requests
 
    graph: { type: Object, notify: true }
 
    song: { type: String, notify: true }
 
    zoomInX: { type: Object, notify: true }
 
    dia: { type: Object }
 
  observers: [
 
    'update(parentAdjs, graph, song, dia)'
 
    'onGraph(graph, song)'
 
    ]
 
  onGraph: (graph, song, zoomInX) ->
 
    graph.runHandler(@update.bind(@))
 
    graph.runHandler(@update.bind(@), "adjuster update")
 
  update: (parentAdjs, graph, song, dia) ->
 
    U = (x) -> @graph.Uri(x)
 
    @adjs = (@parentAdjs || []).slice()
 
    for note in @graph.objects(@song, U(':note'))
 
      @push('adjs', new AdjustableFloatObject({
 
        graph: @graph
 
        subj: note
 
        pred: @graph.Uri(':originTime')
 
        ctx: @graph.Uri(@song)
 
        getTargetTransform: (value) => $V([@zoomInX(value), 600])
 
        getValueForPos: (pos) => @zoomInX.invert(pos.e(1))
 
        getSuggestedTargetOffset: () => $V([0, -80])
 
@@ -393,28 +393,30 @@ Polymer
 
    target:
 
      type: Object
 
      notify: true
 
    displayValue:
 
      type: String
 
    centerStyle:
 
      type: Object
 
    spanClass:
 
      type: String
 
      value: ''
 

	
 
  onAdj: (adj) ->
 
    # currently I think this subscription never gets to matter since
 
    # we might be rebuilding all adj elements on every update
 
    @adj.subscribe(@updateDisplay.bind(this))
 
    @updateDisplay()
 

	
 
  updateDisplay: () ->
 
    window.adjDragUpdates++ if window.adjDragUpdates?
 
    @spanClass = if @adj.config.emptyBox then 'empty' else ''
 
    @displayValue = @adj.getDisplayValue()
 
    center = @adj.getCenter()
 
    target = @adj.getTarget()
 
    return if isNaN(center.e(1))
 
    @centerStyle = {x: center.e(1), y: center.e(2)}
 
    @dia?.setAdjusterConnector(@myId, center, target)
 
        
 
  attached: ->
 
    @myId = 'adjuster-' + _adjusterSerial
 
    _adjusterSerial += 1
 
    
0 comments (0 inline, 0 general)