Changeset - 40bd7f24cae8
[Not reviewed]
default
0 1 0
Drew Perttula - 8 years ago 2017-06-10 09:21:23
drewp@bigasterisk.com
rm autodep watchers code
Ignore-this: 9d16730d3d7f5cf0a59a3089cee01562
1 file changed with 0 insertions and 55 deletions:
0 comments (0 inline, 0 general)
light9/web/graph.coffee
Show inline comments
 
log = console.log
 

	
 
# Patch is {addQuads: <quads>, delQuads: <quads>}
 
# <quads> is [{subject: s, ...}, ...]
 

	
 
# for mocha
 
if require?
 
  `window = {}`
 
  `N3 = require('./lib/N3.js-pull61/N3.js')`
 
  `d3 = require('./lib/d3/build/d3.min.js')`
 
  `RdfDbClient = require('./rdfdbclient.js').RdfDbClient`
 
  module.exports = window
 

	
 
RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
 

	
 
patchSizeSummary = (patch) ->
 
  '-' + patch.delQuads.length + ' +' + patch.addQuads.length
 

	
 
# (sloppily shared to rdfdbclient.coffee too)
 
window.patchSizeSummary = patchSizeSummary
 

	
 
# partial port of autodepgraphapi.py
 
class GraphWatchers # throw this one away; use AutoDependencies
 
  constructor: ->
 
    @handlersSp = {} # {s: {p: [handlers]}}
 
  subscribe: (s, p, o, onChange) -> # return subscription handle
 
    if o? then throw Error('not implemented')
 
    if not @handlersSp[s]
 
      @handlersSp[s] = {}
 
    if not @handlersSp[s][p]
 
      @handlersSp[s][p] = []
 
    @handlersSp[s][p].push(onChange)
 
    handle = {s: s, p: p, func: onChange}
 
    return handle
 
    
 
  unsubscribe: (subscription) ->
 
    spList = @handlersSp[subscription.s][subscription.p]
 
    i = spList.indexOf(subscription.func)
 
    if i == -1
 
      throw new Error('subscription not found')
 
    spList.splice(i, 1)
 

	
 
  matchingHandlers: (quad) ->
 
    matches = []
 
    for subjDict in [@handlersSp[quad.subject] || {}, @handlersSp[null] || {}]
 
      for subjPredMatches in [subjDict[quad.predicate] || [], subjDict[null] || []]
 
        matches = matches.concat(subjPredMatches)
 
    return matches
 
    
 
  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, @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 = 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, label) ->
 
    # what if we have this func already? duplicate is safe?
 

	
 
    if not label?
 
      throw new Error("missing label")
 

	
 
    h = new Handler(func, label)
 
    tailChildren = @handlerStack[@handlerStack.length - 1].innerHandlers
 
    matchingLabel = _.filter(tailChildren, ((c) -> c.label == label)).length
 
    # ohno, something depends on some handlers getting run twice :(
 
    if matchingLabel < 2
 
      tailChildren.push(h)
 
    console.time("handler #{label}")
 
@@ -118,49 +80,48 @@ class AutoDependencies
 
        #child.innerHandlers = [] # let all children get called again
 
        @_rerunHandler(child, patch)
 
        rerunInners(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? 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) ->
 
    # patchSenderUrl is the /syncedGraph path of an rdfdb server.
 
    # prefixes can be used in Uri(curie) calls.
 
    @_watchers = new GraphWatchers() # old
 
    @_autoDeps = new AutoDependencies() # replaces GraphWatchers
 
    @clearGraph()
 

	
 
    if @patchSenderUrl
 
      @_client = new RdfDbClient(@patchSenderUrl, @_clearGraphOnNewConnection.bind(@),
 
                                 @_applyPatch.bind(@), @setStatus)
 
    
 
  clearGraph: ->
 
    # just deletes the statements; watchers are unaffected.
 
    if @graph?
 
      @_applyPatch({addQuads: [], delQuads: @graph.find()})
 

	
 
    # if we had a Store already, this lets N3.Store free all its indices/etc
 
    @graph = N3.Store()
 
    @_addPrefixes(@prefixes)
 
    @cachedFloatValues = new Map();
 

	
 
  _clearGraphOnNewConnection: -> # must not send a patch to the server!
 
    log('graph: clearGraphOnNewConnection')
 
    @clearGraph()
 
    log('graph: clearGraphOnNewConnection done')
 
      
 
  _addPrefixes: (prefixes) ->
 
    @graph.addPrefixes(prefixes)
 
@@ -194,78 +155,62 @@ class window.SyncedGraph
 
    [q.subject, q.predicate, q.object, q.graph] for q in @graph.find()
 

	
 
  applyAndSendPatch: (patch) ->
 
    if !Array.isArray(patch.addQuads) || !Array.isArray(patch.delQuads)
 
      throw new Error("corrupt patch: #{patch}")
 

	
 
    for qs in [patch.addQuads, patch.delQuads]
 
      for q in qs
 
        if not q.graph?
 
          throw new Error("corrupt patch: #{q}")
 

	
 
    @_applyPatch(patch)
 
    @_client.sendPatch(patch) if @_client
 

	
 
  _applyPatch: (patch) ->
 
    # In most cases you want applyAndSendPatch.
 
    # 
 
    # This is the only method that writes to @graph!
 
    @cachedFloatValues.clear()
 
    for quad in patch.delQuads
 
      @graph.removeTriple(quad)
 
    for quad in patch.addQuads
 
      @graph.addTriple(quad)
 
    #log('applied patch locally', patchSizeSummary(patch))
 
    @_watchers.graphChanged(patch)
 
    @_autoDeps.graphChanged(patch)
 

	
 
  getObjectPatch: (s, p, newObject, g) ->
 
    # make a patch which removes existing values for (s,p,*,c) and
 
    # 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, 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).
 

	
 
    # helps with memleak? not sure yet. The point was if two matching
 
    # labels get puushed on, we should run only one. So maybe
 
    # appending a serial number is backwards.
 
    @serial = 1 if not @serial
 
    @serial += 1
 
    #label = label + @serial
 
    
 
    @_autoDeps.runHandler(func, label)
 

	
 
  _singleValue: (s, p) ->
 
    @_autoDeps.askedFor(s, p, null, null)
 
    quads = @graph.findByIRI(s, p)
 
    objs = new Set(q.object for q in quads)
 
    
 
    switch objs.size
 
      when 0
 
        throw new Error("no value for "+s+" "+p)
 
      when 1
 
        obj = objs.values().next().value
0 comments (0 inline, 0 general)