From 67acecd9669f2bb23ba26d72c1b5a5c85b901d9a Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Tue, 18 Jun 2019 11:18:15 -0400 Subject: [PATCH 01/14] wip --- .../scala/wust/webApp/state/GlobalState.scala | 10 +- .../scala/wust/webApp/state/GraphState.scala | 483 ++++++++++++++++++ .../wust/webApp/state/GraphStateSpec.scala | 154 ++++++ 3 files changed, 645 insertions(+), 2 deletions(-) create mode 100644 webApp/src/main/scala/wust/webApp/state/GraphState.scala create mode 100644 webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala diff --git a/webApp/src/main/scala/wust/webApp/state/GlobalState.scala b/webApp/src/main/scala/wust/webApp/state/GlobalState.scala index b5b20f1f7..b8d4a9032 100644 --- a/webApp/src/main/scala/wust/webApp/state/GlobalState.scala +++ b/webApp/src/main/scala/wust/webApp/state/GlobalState.scala @@ -125,8 +125,7 @@ object GlobalState { val g = graph.addNodes( // these nodes are obviously not in the graph for an assumed user, since the user is not persisted yet. // if we start with an assumed user and just create new channels we will never get a graph from the backend. - user().toNode :: - Nil + user().toNode :: Nil ) scribe.debug(" graph with added usernode: " + g) g @@ -136,6 +135,13 @@ object GlobalState { } } + val graphState = Var(new GraphState(Graph.empty)) + eventProcessor.graphEvents.foreach { + case LocalGraphUpdateEvent.NewChanges(changes) => graphState.now.update(changes) + case LocalGraphUpdateEvent.NewGraph(graph) => graphState() = new GraphState(graph) + } + + val viewConfig: Rx[ViewConfig] = { var lastViewConfig: ViewConfig = ViewConfig(View.Empty, Page.empty) diff --git a/webApp/src/main/scala/wust/webApp/state/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/GraphState.scala new file mode 100644 index 000000000..d52f7c780 --- /dev/null +++ b/webApp/src/main/scala/wust/webApp/state/GraphState.scala @@ -0,0 +1,483 @@ +package wust.webApp.state + +import rx._ +import flatland._ +import wust.ids._ +import wust.util.algorithm._ +import wust.util.collection._ +import wust.util.macros.InlineList +import wust.graph._ +import wust.util.time.time + +import scala.collection.{ breakOut, immutable, mutable } + +//TODO: idempotence +//TODO: commutativity (e.g. store edges without loaded nodes until the nodes appear) + +class GraphState(initialGraph: Graph) { + import initialGraph.{ nodes, edges } + val nodeState = NodeState(initialGraph.nodes) + val edgeState = EdgeState(initialGraph.edges) + import nodeState.idToIdxForeach + + val n = initialGraph.size + private val consistentEdges = ArraySet.create(edges.length) + val edgesIdx = InterleavedArrayInt.create(edges.length) + + // TODO: have one big triple nested array for all edge lookups? + + // To avoid array builders for each node, we collect the node degrees in a + // loop and then add the indices in a second loop. This is twice as fast + // than using one loop with arraybuilders. (A lot less allocations) + // private val outDegree = new Array[Int](n) + // private val parentsDegree = new Array[Int](n) + // private val contentsDegree = new Array[Int](n) + private val readDegree = new Array[Int](n) + private val childrenDegree = new Array[Int](n) + // private val messageChildrenDegree = new Array[Int](n) + // private val taskChildrenDegree = new Array[Int](n) + // private val noteChildrenDegree = new Array[Int](n) + // private val projectChildrenDegree = new Array[Int](n) + // private val tagChildrenDegree = new Array[Int](n) + // private val tagParentsDegree = new Array[Int](n) + // private val stageParentsDegree = new Array[Int](n) + // private val notDeletedParentsDegree = new Array[Int](n) + // private val notDeletedChildrenDegree = new Array[Int](n) + // private val authorshipDegree = new Array[Int](n) + // private val membershipsForNodeDegree = new Array[Int](n) + // private val notifyByUserDegree = new Array[Int](n) + // private val pinnedNodeDegree = new Array[Int](n) + // private val inviteNodeDegree = new Array[Int](n) + // private val expandedEdgesDegree = new Array[Int](n) + // private val assignedNodesDegree = new Array[Int](n) + // private val assignedUsersDegree = new Array[Int](n) + // private val propertiesDegree = new Array[Int](n) + // private val propertiesReverseDegree = new Array[Int](n) + // private val automatedDegree = new Array[Int](n) + // private val automatedReverseDegree = new Array[Int](n) + // private val derivedFromTemplateDegree = new Array[Int](n) + + private val buildNow = EpochMilli.now + + edges.foreachIndexAndElement { (edgeIdx, edge) => + idToIdxForeach(edge.sourceId) { sourceIdx => + idToIdxForeach(edge.targetId) { targetIdx => + consistentEdges.add(edgeIdx) + edgesIdx.updatea(edgeIdx, sourceIdx) + edgesIdx.updateb(edgeIdx, targetIdx) + // outDegree(sourceIdx) += 1 + // edge match { + // case e: Edge.Content => contentsDegree(sourceIdx) += 1 + // case _ => + // } + + edge match { + // case _: Edge.Author => + // authorshipDegree(sourceIdx) += 1 + // case _: Edge.Member => + // membershipsForNodeDegree(sourceIdx) += 1 + case e: Edge.Child => + // val childIsMessage = nodes(targetIdx).role == NodeRole.Message + // val childIsTask = nodes(targetIdx).role == NodeRole.Task + // val childIsNote = nodes(targetIdx).role == NodeRole.Note + // val childIsProject = nodes(targetIdx).role == NodeRole.Project + // val childIsTag = nodes(targetIdx).role == NodeRole.Tag + // val parentIsTag = nodes(sourceIdx).role == NodeRole.Tag + // val parentIsStage = nodes(sourceIdx).role == NodeRole.Stage + // parentsDegree(targetIdx) += 1 + childrenDegree(sourceIdx) += 1 + + // if (childIsProject) projectChildrenDegree(sourceIdx) += 1 + // if (childIsMessage) messageChildrenDegree(sourceIdx) += 1 + // if (childIsTask) taskChildrenDegree(sourceIdx) += 1 + // if (childIsNote) noteChildrenDegree(sourceIdx) += 1 + + // e.data.deletedAt match { + // case None => + // if (childIsTag) tagChildrenDegree(sourceIdx) += 1 + // if (parentIsTag) tagParentsDegree(targetIdx) += 1 + // if (parentIsStage) stageParentsDegree(targetIdx) += 1 + // notDeletedParentsDegree(targetIdx) += 1 + // notDeletedChildrenDegree(sourceIdx) += 1 + // case Some(deletedAt) => + // if (deletedAt isAfter buildNow) { // in the future + // if (childIsTag) tagChildrenDegree(sourceIdx) += 1 + // if (parentIsTag) tagParentsDegree(targetIdx) += 1 + // if (parentIsStage) stageParentsDegree(targetIdx) += 1 + // notDeletedParentsDegree(targetIdx) += 1 + // notDeletedChildrenDegree(sourceIdx) += 1 + // } + // // TODO everything deleted further in the past should already be filtered in backend + // // BUT received on request + // } + // case _: Edge.Assigned => + // assignedNodesDegree(targetIdx) += 1 + // assignedUsersDegree(sourceIdx) += 1 + // case _: Edge.Expanded => + // expandedEdgesDegree(sourceIdx) += 1 + // case _: Edge.Notify => + // notifyByUserDegree(targetIdx) += 1 + // case _: Edge.Pinned => + // pinnedNodeDegree(targetIdx) += 1 + // case _: Edge.Invite => + // inviteNodeDegree(targetIdx) += 1 + // case _: Edge.LabeledProperty => + // propertiesDegree(sourceIdx) += 1 + // propertiesReverseDegree(targetIdx) += 1 + // case _: Edge.Automated => + // automatedDegree(sourceIdx) += 1 + // automatedReverseDegree(targetIdx) += 1 + // case _: Edge.DerivedFromTemplate => + // derivedFromTemplateDegree(sourceIdx) += 1 + case _: Edge.Read => + readDegree(sourceIdx) += 1 + case _ => + } + } + } + } + + // private val outgoingEdgeIdxBuilder = NestedArrayInt.builder(outDegree) + // private val parentsIdxBuilder = NestedArrayInt.builder(parentsDegree) + // private val parentEdgeIdxBuilder = NestedArrayInt.builder(parentsDegree) + // private val contentsEdgeIdxBuilder = NestedArrayInt.builder(contentsDegree) + private val readEdgeIdxBuilder = NestedArrayInt.builder(readDegree) + private val childrenIdxBuilder = NestedArrayInt.builder(childrenDegree) + // private val childEdgeIdxBuilder = NestedArrayInt.builder(childrenDegree) + // private val messageChildrenIdxBuilder = NestedArrayInt.builder(messageChildrenDegree) + // private val taskChildrenIdxBuilder = NestedArrayInt.builder(taskChildrenDegree) + // private val noteChildrenIdxBuilder = NestedArrayInt.builder(noteChildrenDegree) + // private val projectChildrenIdxBuilder = NestedArrayInt.builder(projectChildrenDegree) + // private val tagChildrenIdxBuilder = NestedArrayInt.builder(tagChildrenDegree) + // private val tagParentsIdxBuilder = NestedArrayInt.builder(tagParentsDegree) + // private val stageParentsIdxBuilder = NestedArrayInt.builder(stageParentsDegree) + // private val notDeletedParentsIdxBuilder = NestedArrayInt.builder(notDeletedParentsDegree) + // private val notDeletedChildrenIdxBuilder = NestedArrayInt.builder(notDeletedChildrenDegree) + // private val authorshipEdgeIdxBuilder = NestedArrayInt.builder(authorshipDegree) + // private val authorIdxBuilder = NestedArrayInt.builder(authorshipDegree) + // private val membershipEdgeForNodeIdxBuilder = NestedArrayInt.builder(membershipsForNodeDegree) + // private val notifyByUserIdxBuilder = NestedArrayInt.builder(notifyByUserDegree) + // private val pinnedNodeIdxBuilder = NestedArrayInt.builder(pinnedNodeDegree) + // private val inviteNodeIdxBuilder = NestedArrayInt.builder(inviteNodeDegree) + // private val expandedEdgeIdxBuilder = NestedArrayInt.builder(expandedEdgesDegree) + // private val assignedNodesIdxBuilder = NestedArrayInt.builder(assignedNodesDegree) + // private val assignedUsersIdxBuilder = NestedArrayInt.builder(assignedUsersDegree) + // private val propertiesEdgeIdxBuilder = NestedArrayInt.builder(propertiesDegree) + // private val propertiesEdgeReverseIdxBuilder = NestedArrayInt.builder(propertiesReverseDegree) + // private val automatedEdgeIdxBuilder = NestedArrayInt.builder(automatedDegree) + // private val automatedEdgeReverseIdxBuilder = NestedArrayInt.builder(automatedReverseDegree) + // private val derivedFromTemplateEdgeIdxBuilder = NestedArrayInt.builder(derivedFromTemplateDegree) + + consistentEdges.foreach { edgeIdx => + val sourceIdx = edgesIdx.a(edgeIdx) + val targetIdx = edgesIdx.b(edgeIdx) + val edge = edges(edgeIdx) + // outgoingEdgeIdxBuilder.add(sourceIdx, edgeIdx) + + // edge match { + // case e: Edge.Content => contentsEdgeIdxBuilder.add(sourceIdx, edgeIdx) + + // case _ => + // } + + edge match { + // case _: Edge.Author => + // authorshipEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // authorIdxBuilder.add(sourceIdx, targetIdx) + // case _: Edge.Member => + // membershipEdgeForNodeIdxBuilder.add(sourceIdx, edgeIdx) + case e: Edge.Child => + // val childIsMessage = nodes(targetIdx).role == NodeRole.Message + // val childIsTask = nodes(targetIdx).role == NodeRole.Task + // val childIsNote = nodes(targetIdx).role == NodeRole.Note + // val childIsTag = nodes(targetIdx).role == NodeRole.Tag + // val childIsProject = nodes(targetIdx).role == NodeRole.Project + // val parentIsTag = nodes(sourceIdx).role == NodeRole.Tag + // val parentIsStage = nodes(sourceIdx).role == NodeRole.Stage + // parentsIdxBuilder.add(targetIdx, sourceIdx) + // parentEdgeIdxBuilder.add(targetIdx, edgeIdx) + childrenIdxBuilder.add(sourceIdx, targetIdx) + // childEdgeIdxBuilder.add(sourceIdx, edgeIdx) + + // if (childIsProject) projectChildrenIdxBuilder.add(sourceIdx, targetIdx) + // if (childIsMessage) messageChildrenIdxBuilder.add(sourceIdx, targetIdx) + // if (childIsTask) taskChildrenIdxBuilder.add(sourceIdx, targetIdx) + // if (childIsNote) noteChildrenIdxBuilder.add(sourceIdx, targetIdx) + + // e.data.deletedAt match { + // case None => + // if (childIsTag) tagChildrenIdxBuilder.add(sourceIdx, targetIdx) + // if (parentIsTag) tagParentsIdxBuilder.add(targetIdx, sourceIdx) + // if (parentIsStage) stageParentsIdxBuilder.add(targetIdx, sourceIdx) + // notDeletedParentsIdxBuilder.add(targetIdx, sourceIdx) + // notDeletedChildrenIdxBuilder.add(sourceIdx, targetIdx) + // case Some(deletedAt) => + // if (deletedAt isAfter buildNow) { // in the future + // if (childIsTag) tagChildrenIdxBuilder.add(sourceIdx, targetIdx) + // if (parentIsTag) tagParentsIdxBuilder.add(targetIdx, sourceIdx) + // if (parentIsStage) stageParentsIdxBuilder.add(targetIdx, sourceIdx) + // notDeletedParentsIdxBuilder.add(targetIdx, sourceIdx) + // notDeletedChildrenIdxBuilder.add(sourceIdx, targetIdx) + // } + // // TODO everything deleted further in the past should already be filtered in backend + // // BUT received on request + // } + // case _: Edge.Expanded => + // expandedEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // case _: Edge.Assigned => + // assignedNodesIdxBuilder.add(targetIdx, sourceIdx) + // assignedUsersIdxBuilder.add(sourceIdx, targetIdx) + // case _: Edge.Notify => + // notifyByUserIdxBuilder.add(targetIdx, sourceIdx) + // case _: Edge.Pinned => + // pinnedNodeIdxBuilder.add(targetIdx, sourceIdx) + // case _: Edge.Invite => + // inviteNodeIdxBuilder.add(targetIdx, sourceIdx) + // case _: Edge.LabeledProperty => + // propertiesEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // propertiesEdgeReverseIdxBuilder.add(targetIdx, edgeIdx) + // case _: Edge.Automated => + // automatedEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // automatedEdgeReverseIdxBuilder.add(targetIdx, edgeIdx) + // case _: Edge.DerivedFromTemplate => + // derivedFromTemplateEdgeIdxBuilder.add(sourceIdx, edgeIdx) + case _: Edge.Read => + readEdgeIdxBuilder.add(sourceIdx, edgeIdx) + case _ => + } + } + + // val outgoingEdgeIdx: NestedArrayInt = outgoingEdgeIdxBuilder.result() + // val parentsIdx: NestedArrayInt = parentsIdxBuilder.result() + // val parentEdgeIdx: NestedArrayInt = parentEdgeIdxBuilder.result() + val readEdgeIdx: NestedArrayInt = readEdgeIdxBuilder.result() + val childrenIdx: NestedArrayInt = childrenIdxBuilder.result() + // val childEdgeIdx: NestedArrayInt = childEdgeIdxBuilder.result() + // val contentsEdgeIdx: NestedArrayInt = contentsEdgeIdxBuilder.result() + // val messageChildrenIdx: NestedArrayInt = messageChildrenIdxBuilder.result() + // val taskChildrenIdx: NestedArrayInt = taskChildrenIdxBuilder.result() + // val noteChildrenIdx: NestedArrayInt = noteChildrenIdxBuilder.result() + // val tagChildrenIdx: NestedArrayInt = tagChildrenIdxBuilder.result() + // val projectChildrenIdx: NestedArrayInt = projectChildrenIdxBuilder.result() + // val tagParentsIdx: NestedArrayInt = tagParentsIdxBuilder.result() + // val stageParentsIdx: NestedArrayInt = stageParentsIdxBuilder.result() + // val notDeletedParentsIdx: NestedArrayInt = notDeletedParentsIdxBuilder.result() + // val notDeletedChildrenIdx: NestedArrayInt = notDeletedChildrenIdxBuilder.result() + // val authorshipEdgeIdx: NestedArrayInt = authorshipEdgeIdxBuilder.result() + // val membershipEdgeForNodeIdx: NestedArrayInt = membershipEdgeForNodeIdxBuilder.result() + // val notifyByUserIdx: NestedArrayInt = notifyByUserIdxBuilder.result() + // val authorsIdx: NestedArrayInt = authorIdxBuilder.result() + // val pinnedNodeIdx: NestedArrayInt = pinnedNodeIdxBuilder.result() + // val inviteNodeIdx: NestedArrayInt = inviteNodeIdxBuilder.result() + // val expandedEdgeIdx: NestedArrayInt = expandedEdgeIdxBuilder.result() + // val assignedNodesIdx: NestedArrayInt = assignedNodesIdxBuilder.result() // user -> node + // val assignedUsersIdx: NestedArrayInt = assignedUsersIdxBuilder.result() // node -> user + // val propertiesEdgeIdx: NestedArrayInt = propertiesEdgeIdxBuilder.result() // node -> property edge + // val propertiesEdgeReverseIdx: NestedArrayInt = propertiesEdgeReverseIdxBuilder.result() // node -> property edge + // val automatedEdgeIdx: NestedArrayInt = automatedEdgeIdxBuilder.result() + // val automatedEdgeReverseIdx: NestedArrayInt = automatedEdgeReverseIdxBuilder.result() + // val derivedFromTemplateEdgeIdx: NestedArrayInt = derivedFromTemplateEdgeIdxBuilder.result() + val children = new ChildrenLayer(childrenIdx) + val read = new ChildrenLayer(childrenIdx) + + // private val sortedAuthorshipEdgeIdx: NestedArrayInt = NestedArrayInt(authorshipEdgeIdx.map(slice => slice.sortBy(author => edges(author).as[Edge.Author].data.timestamp).toArray)(breakOut) : Array[Array[Int]]) + + // private val (nodeCreated: mutable.ArrayBuffer[Var[EpochMilli]], nodeCreatorIdx: mutable.ArrayBuffer[Int], nodeModified: mutable.ArrayBuffer[Var[EpochMilli]]) = { + // val nodeCreator = new mutable.ArrayBuffer[Int](n) + // val nodeCreated = new mutable.ArrayBuffer.fill[Var[EpochMilli]](n)(Var(EpochMilli.min)) // filled with 0L = EpochMilli.min by default + // val nodeModified = new mutable.ArrayBuffer.fill[Var[EpochMilli]](n)(Var(EpochMilli.min)) // filled with 0L = EpochMilli.min by default + // var nodeIdx = 0 + // while (nodeIdx < n) { + // val authorEdgeIndices: ArraySliceInt = sortedAuthorshipEdgeIdx(nodeIdx) + // if (authorEdgeIndices.nonEmpty) { + // val (createdEdgeIdx, lastModifierEdgeIdx) = (authorEdgeIndices.head, authorEdgeIndices.last) + // nodeCreated(nodeIdx) = edges(createdEdgeIdx).as[Edge.Author].data.timestamp + // nodeCreator(nodeIdx) = edgesIdx.b(createdEdgeIdx) + // nodeModified(nodeIdx) = edges(lastModifierEdgeIdx).as[Edge.Author].data.timestamp + // } else { + // nodeCreator(nodeIdx) = -1 //TODO: we do not want -1 indices... + // } + // nodeIdx += 1 + // } + // (nodeCreated, nodeCreator, nodeModified) + // } + // def nodeDeepCreated(nodeIdx:Int):Rx[EpochMilli] = { + // var created = nodeCreated(nodeIdx) + // dfs.foreach(_(nodeIdx), dfs.withoutStart, childrenIdx, { childIdx => + // val childCreated = nodeCreated(childIdx) + // if(childCreated isAfter created) created = childCreated + // }) + // created + // } + + def update(changes: GraphChanges) = { + time("graphstate") { + edgeState.update(changes) + val layerChanges = nodeState.update(changes) + children.update(nodeState, layerChanges) + read.update(nodeState, layerChanges) + } + } +} + +object NodeState { + def apply(graphNodes: Array[Node]) = { + val nodes = mutable.ArrayBuffer.empty[Node] + val idToIdxHashMap = mutable.HashMap.empty[NodeId, Int] + idToIdxHashMap.sizeHint(graphNodes.length) + + graphNodes.foreachIndexAndElement { (idx, node) => + val nodeId = node.id + nodes += node + idToIdxHashMap(nodeId) = idx + } + new NodeState(nodes, idToIdxHashMap) + } +} + +class NodeState private ( + val nodesNow: mutable.ArrayBuffer[Node], + val idToIdxHashMap: mutable.HashMap[NodeId, Int] +) { + val nodesRx: mutable.ArrayBuffer[Var[Node]] = nodesNow.map(Var(_)) + + @inline def idToIdxFold[T](id: NodeId)(default: => T)(f: Int => T): T = { + idToIdxHashMap.get(id) match { + case Some(idx) => f(idx) + case None => default + } + } + @inline def idToIdxForeach[U](id: NodeId)(f: Int => U): Unit = idToIdxFold(id)(())(f(_)) + @inline def idToIdxMap[T](id: NodeId)(f: Int => T): Option[T] = idToIdxFold(id)(Option.empty[T])(idx => Some(f(idx))) + @inline def idToIdxOrThrow(nodeId: NodeId): Int = idToIdxHashMap(nodeId) + def idToIdx(nodeId: NodeId): Option[Int] = idToIdxFold[Option[Int]](nodeId)(None)(Some(_)) + def nodesByIdOrThrow(nodeId: NodeId): Node = nodesNow(idToIdxOrThrow(nodeId)) + def nodesById(nodeId: NodeId): Option[Node] = idToIdxFold[Option[Node]](nodeId)(None)(idx => Some(nodesNow(idx))) + + def update(changes: GraphChanges): LayerChanges = { + // register new and updated nodes + + var addIdx = 0 // counts the number of newly added nodes + changes.addNodes.foreachElement { node => + val nodeId = node.id + idToIdxFold(nodeId){ + // add new node and update idToIdxHashMap + val newIdx = nodesNow.length + nodesNow += node + nodesRx += Var(node) + idToIdxHashMap(nodeId) = newIdx + addIdx += 1 + }{ idx => + // already exists, update node + nodesNow(idx) = node + nodesRx(idx)() = node + } + } + + assert(nodesNow.length == idToIdxHashMap.size) + assert(nodesNow.length == nodesRx.length) + LayerChanges(addIdx, changes.addEdges, changes.delEdges) + } +} + +class EdgeState private ( + val edgesNow: mutable.ArrayBuffer[Edge], + val idToIdxHashMap: mutable.HashMap[(NodeId, NodeId), Int] +) { + val edgesRx: mutable.ArrayBuffer[Var[Edge]] = edgesNow.map(Var(_)) + def update(changes: GraphChanges): Unit = { + // register new and updated edges + + changes.addEdges.foreachElement { edge => + val key = edge.sourceId -> edge.targetId + + idToIdxHashMap.get(key) match { + case Some(idx) => + edgesNow(idx) = edge + edgesRx(idx)() = edge + case None => + val newIdx = edgesNow.length + edgesNow += edge + edgesRx += Var(edge) + idToIdxHashMap(key) = newIdx + } + + assert(edgesNow.length == idToIdxHashMap.size) + } + } +} + +final case class LayerChanges( + addIdx: Int, + addEdges: Array[Edge] = Array.empty, + delEdges: Array[Edge] = Array.empty +) + +abstract class LayerState { + var lookupNow: NestedArrayInt + val lookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = lookupNow.map(slice => Var(slice.toArray))(breakOut) + + @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] + + def update(nodeState: NodeState, changes: LayerChanges): Unit = { + val affectedSourceNodes = new mutable.ArrayBuffer[Int] + val addElemBuilder = new mutable.ArrayBuilder.ofInt + changes.addEdges.foreach { + ifMyEdge { (sourceId, targetId) => + nodeState.idToIdxForeach(sourceId) { sourceIdx => + nodeState.idToIdxForeach(targetId) { targetIdx => + addElemBuilder += sourceIdx + addElemBuilder += targetIdx + affectedSourceNodes += sourceIdx + } + } + } + } + val addElem = new InterleavedArrayInt(addElemBuilder.result()) + + val delElemBuilder = new mutable.ArrayBuilder.ofInt + changes.delEdges.foreach { + ifMyEdge { (sourceId, targetId) => + nodeState.idToIdxForeach(sourceId) { sourceIdx => + nodeState.idToIdxForeach(targetId) { targetIdx => + delElemBuilder += sourceIdx + delElemBuilder += lookupNow.indexOf(sourceIdx)(targetIdx) // Remove the first occurence of the sourceId/targetId combination + affectedSourceNodes += sourceIdx + } + } + } + } + val delElem = new InterleavedArrayInt(delElemBuilder.result()) + + // NestedArray.changed() parameters: + // addIdx: Int, // how many nodes are added + // addElem: InterleavedArrayInt // Array[idx -> elem] + // delElem: InterleavedArrayInt // Array[idx -> position] + // if (scala.scalajs.LinkingInfo.developmentMode) + // lookupNow = lookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) + // else + lookupNow = lookupNow.changed(changes.addIdx, addElem, delElem) + + loop(changes.addIdx) { _ => + lookupRx += Var(new Array[Int](0)) + } + + affectedSourceNodes.foreachElement { idx => + lookupRx(idx)() = lookupNow(idx).toArray + } + } +} + +final class ChildrenLayer(var lookupNow: NestedArrayInt) extends LayerState { + @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { + case edge: Edge.Child => code(edge.parentId, edge.childId) + case _ => + } +} + +final class ReadLayer(var lookupNow: NestedArrayInt) extends LayerState { + @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { + case edge: Edge.Read => code(edge.nodeId, edge.userId) + case _ => + } +} diff --git a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala new file mode 100644 index 000000000..395672941 --- /dev/null +++ b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala @@ -0,0 +1,154 @@ +package wust.webApp.state + +import org.scalatest._ +import wust.graph._ +import wust.ids._ + +class IncrementalReactiveGraphSpec extends FreeSpec with MustMatchers { + def parent(childId:Cuid, parentId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) + def child(parentId:Cuid, childId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) + def user(id:Cuid) = Node.User(UserId(NodeId(id)), NodeData.User(id.toString, false, 0), NodeMeta.User) + implicit def stringToCuid(id:String):Cuid = Cuid.fromBase58String("5Q4is6Gc5NbA7T7W7PvAUw".dropRight(id.length) + id).right.get + implicit def idToNode(id: String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText("content"), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + def idToNode(id: String, content:String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText(content), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + + "GraphState factory" in { + val graph = Graph( + nodes = Array("A", "B", "C", "D", "E", "F"), + edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + ) + + val graphState = new GraphState(graph) + import graphState.{nodeState, children} + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("B":Cuid))) == idToNode("B")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("C":Cuid))) == idToNode("C")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("D":Cuid))) == idToNode("D")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("E":Cuid))) == idToNode("E")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("F":Cuid))) == idToNode("F")) + + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now == idToNode("B")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now == idToNode("C")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now == idToNode("D")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now == idToNode("E")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now == idToNode("F")) + + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List()) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("F")) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("E")) + + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("F")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("E")) + } + + "GraphState add nodes" in { + val graph = Graph( + nodes = Array("A", "B", "C", "D", "E", "F"), + edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + ) + + val graphState = new GraphState(graph) + graphState.update(GraphChanges(addNodes = Array[Node]("G", "H"))) + import graphState.{nodeState, children} + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("B":Cuid))) == idToNode("B")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("C":Cuid))) == idToNode("C")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("D":Cuid))) == idToNode("D")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("E":Cuid))) == idToNode("E")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("F":Cuid))) == idToNode("F")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("G":Cuid))) == idToNode("G")) + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("H":Cuid))) == idToNode("H")) + + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now == idToNode("B")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now == idToNode("C")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now == idToNode("D")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now == idToNode("E")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now == idToNode("F")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now == idToNode("G")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("H":Cuid))).now == idToNode("H")) + + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("F")) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("E")) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("F")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("E")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + } + + "GraphState update node" in { + val graph = Graph( + nodes = Array("A", "B", "C", "D", "E", "F"), + edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + ) + + val graphState = new GraphState(graph) + import graphState.{nodeState, children} + + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A")) + + graphState.update(GraphChanges(addNodes = Array[Node](idToNode("A", "changed")))) + + assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A", "changed")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A", "changed")) + } + + "GraphState add edge" in { + val graph = Graph( + nodes = Array("A", "B", "C", "D", "E", "F"), + edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + ) + + val graphState = new GraphState(graph) + import graphState.{nodeState, children} + + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + + graphState.update(GraphChanges(addEdges = Array(child("A", "C")))) + + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C")) + } + + "GraphState add nodes with edge" in { + val graph = Graph( + nodes = Array("A", "B", "C", "D", "E", "F"), + edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + ) + + val graphState = new GraphState(graph) + import graphState.{nodeState, children} + + graphState.update(GraphChanges(addNodes = Array("G","H"), addEdges = Array(child("G", "H")))) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("H")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("H")) + } + + "GraphState delete edge" in { + val graph = Graph( + nodes = Array("A", "B", "C", "D", "E", "F"), + edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + ) + + val graphState = new GraphState(graph) + import graphState.{nodeState, children} + + graphState.update(GraphChanges(delEdges = Array(child("E", "F")))) + assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + } + + //TODO: delete for author LabeledProperty edges, where edges can have the same source/target combination + //TODO: addEdges overwrite edge +} From fc818029db51a6497a8286cdb2ae450bd910eaa7 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Thu, 11 Jul 2019 13:51:58 -0400 Subject: [PATCH 02/14] wip --- .../scala/wust/webApp/state/GlobalState.scala | 1 + .../webApp/state/graphstate/EdgeState.scala | 56 ++++++ .../state/{ => graphstate}/GraphState.scala | 159 +----------------- .../webApp/state/graphstate/LayerState.scala | 73 ++++++++ .../webApp/state/graphstate/NodeState.scala | 72 ++++++++ 5 files changed, 208 insertions(+), 153 deletions(-) create mode 100644 webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala rename webApp/src/main/scala/wust/webApp/state/{ => graphstate}/GraphState.scala (76%) create mode 100644 webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala create mode 100644 webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala diff --git a/webApp/src/main/scala/wust/webApp/state/GlobalState.scala b/webApp/src/main/scala/wust/webApp/state/GlobalState.scala index b8d4a9032..7e4a8d1d7 100644 --- a/webApp/src/main/scala/wust/webApp/state/GlobalState.scala +++ b/webApp/src/main/scala/wust/webApp/state/GlobalState.scala @@ -2,6 +2,7 @@ package wust.webApp.state import scala.util.Try import com.github.ghik.silencer.silent +import graphstate.GraphState import acyclic.file import monix.reactive.Observable import monix.reactive.subjects.PublishSubject diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala new file mode 100644 index 000000000..82a2ed129 --- /dev/null +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala @@ -0,0 +1,56 @@ +package wust.webApp.state.graphstate + +import rx._ +import flatland._ +import wust.ids._ +import wust.util.algorithm._ +import wust.util.collection._ +import wust.util.macros.InlineList +import wust.graph._ +import wust.util.time.time + +import scala.collection.{ breakOut, immutable, mutable } + +object EdgeState { + def apply(graphEdges: Array[Edge]) = { + val edges = mutable.ArrayBuffer.empty[Edge] + val idToIdxHashMap = mutable.HashMap.empty[(NodeId, NodeId), Int] + idToIdxHashMap.sizeHint(graphEdges.length) + + graphEdges.foreachIndexAndElement { (idx, edge) => + edges += edge + idToIdxHashMap(edgeKey(edge)) = idx + } + new EdgeState(edges, idToIdxHashMap) + } + + @inline def edgeKey(edge: Edge): (NodeId, NodeId) = edge.sourceId -> edge.targetId +} + +final class EdgeState private ( + val edgesNow: mutable.ArrayBuffer[Edge], + val idToIdxHashMap: mutable.HashMap[(NodeId, NodeId), Int] +) { + val edgesRx: mutable.ArrayBuffer[Var[Edge]] = edgesNow.map(Var(_)) + def update(changes: GraphChanges): Unit = { + // register new and updated edges + + changes.addEdges.foreachElement { edge => + val key = EdgeState.edgeKey(edge) + + idToIdxHashMap.get(key) match { + case Some(idx) => + edgesNow(idx) = edge + edgesRx(idx)() = edge + case None => + val newIdx = edgesNow.length + edgesNow += edge + edgesRx += Var(edge) + idToIdxHashMap(key) = newIdx + } + + assert(edgesNow.length == idToIdxHashMap.size) + } + } +} + diff --git a/webApp/src/main/scala/wust/webApp/state/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala similarity index 76% rename from webApp/src/main/scala/wust/webApp/state/GraphState.scala rename to webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index d52f7c780..028adc030 100644 --- a/webApp/src/main/scala/wust/webApp/state/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -1,4 +1,4 @@ -package wust.webApp.state +package wust.webApp.state.graphstate import rx._ import flatland._ @@ -14,7 +14,7 @@ import scala.collection.{ breakOut, immutable, mutable } //TODO: idempotence //TODO: commutativity (e.g. store edges without loaded nodes until the nodes appear) -class GraphState(initialGraph: Graph) { +final class GraphState(initialGraph: Graph) { import initialGraph.{ nodes, edges } val nodeState = NodeState(initialGraph.nodes) val edgeState = EdgeState(initialGraph.edges) @@ -250,8 +250,8 @@ class GraphState(initialGraph: Graph) { // val outgoingEdgeIdx: NestedArrayInt = outgoingEdgeIdxBuilder.result() // val parentsIdx: NestedArrayInt = parentsIdxBuilder.result() // val parentEdgeIdx: NestedArrayInt = parentEdgeIdxBuilder.result() - val readEdgeIdx: NestedArrayInt = readEdgeIdxBuilder.result() - val childrenIdx: NestedArrayInt = childrenIdxBuilder.result() + val readEdgeIdx: NestedArrayIntValues = readEdgeIdxBuilder.result() + val childrenIdx: NestedArrayIntValues = childrenIdxBuilder.result() // val childEdgeIdx: NestedArrayInt = childEdgeIdxBuilder.result() // val contentsEdgeIdx: NestedArrayInt = contentsEdgeIdxBuilder.result() // val messageChildrenIdx: NestedArrayInt = messageChildrenIdxBuilder.result() @@ -320,162 +320,15 @@ class GraphState(initialGraph: Graph) { } } -object NodeState { - def apply(graphNodes: Array[Node]) = { - val nodes = mutable.ArrayBuffer.empty[Node] - val idToIdxHashMap = mutable.HashMap.empty[NodeId, Int] - idToIdxHashMap.sizeHint(graphNodes.length) - graphNodes.foreachIndexAndElement { (idx, node) => - val nodeId = node.id - nodes += node - idToIdxHashMap(nodeId) = idx - } - new NodeState(nodes, idToIdxHashMap) - } -} - -class NodeState private ( - val nodesNow: mutable.ArrayBuffer[Node], - val idToIdxHashMap: mutable.HashMap[NodeId, Int] -) { - val nodesRx: mutable.ArrayBuffer[Var[Node]] = nodesNow.map(Var(_)) - - @inline def idToIdxFold[T](id: NodeId)(default: => T)(f: Int => T): T = { - idToIdxHashMap.get(id) match { - case Some(idx) => f(idx) - case None => default - } - } - @inline def idToIdxForeach[U](id: NodeId)(f: Int => U): Unit = idToIdxFold(id)(())(f(_)) - @inline def idToIdxMap[T](id: NodeId)(f: Int => T): Option[T] = idToIdxFold(id)(Option.empty[T])(idx => Some(f(idx))) - @inline def idToIdxOrThrow(nodeId: NodeId): Int = idToIdxHashMap(nodeId) - def idToIdx(nodeId: NodeId): Option[Int] = idToIdxFold[Option[Int]](nodeId)(None)(Some(_)) - def nodesByIdOrThrow(nodeId: NodeId): Node = nodesNow(idToIdxOrThrow(nodeId)) - def nodesById(nodeId: NodeId): Option[Node] = idToIdxFold[Option[Node]](nodeId)(None)(idx => Some(nodesNow(idx))) - - def update(changes: GraphChanges): LayerChanges = { - // register new and updated nodes - - var addIdx = 0 // counts the number of newly added nodes - changes.addNodes.foreachElement { node => - val nodeId = node.id - idToIdxFold(nodeId){ - // add new node and update idToIdxHashMap - val newIdx = nodesNow.length - nodesNow += node - nodesRx += Var(node) - idToIdxHashMap(nodeId) = newIdx - addIdx += 1 - }{ idx => - // already exists, update node - nodesNow(idx) = node - nodesRx(idx)() = node - } - } - - assert(nodesNow.length == idToIdxHashMap.size) - assert(nodesNow.length == nodesRx.length) - LayerChanges(addIdx, changes.addEdges, changes.delEdges) - } -} - -class EdgeState private ( - val edgesNow: mutable.ArrayBuffer[Edge], - val idToIdxHashMap: mutable.HashMap[(NodeId, NodeId), Int] -) { - val edgesRx: mutable.ArrayBuffer[Var[Edge]] = edgesNow.map(Var(_)) - def update(changes: GraphChanges): Unit = { - // register new and updated edges - - changes.addEdges.foreachElement { edge => - val key = edge.sourceId -> edge.targetId - - idToIdxHashMap.get(key) match { - case Some(idx) => - edgesNow(idx) = edge - edgesRx(idx)() = edge - case None => - val newIdx = edgesNow.length - edgesNow += edge - edgesRx += Var(edge) - idToIdxHashMap(key) = newIdx - } - - assert(edgesNow.length == idToIdxHashMap.size) - } - } -} - -final case class LayerChanges( - addIdx: Int, - addEdges: Array[Edge] = Array.empty, - delEdges: Array[Edge] = Array.empty -) - -abstract class LayerState { - var lookupNow: NestedArrayInt - val lookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = lookupNow.map(slice => Var(slice.toArray))(breakOut) - - @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] - - def update(nodeState: NodeState, changes: LayerChanges): Unit = { - val affectedSourceNodes = new mutable.ArrayBuffer[Int] - val addElemBuilder = new mutable.ArrayBuilder.ofInt - changes.addEdges.foreach { - ifMyEdge { (sourceId, targetId) => - nodeState.idToIdxForeach(sourceId) { sourceIdx => - nodeState.idToIdxForeach(targetId) { targetIdx => - addElemBuilder += sourceIdx - addElemBuilder += targetIdx - affectedSourceNodes += sourceIdx - } - } - } - } - val addElem = new InterleavedArrayInt(addElemBuilder.result()) - - val delElemBuilder = new mutable.ArrayBuilder.ofInt - changes.delEdges.foreach { - ifMyEdge { (sourceId, targetId) => - nodeState.idToIdxForeach(sourceId) { sourceIdx => - nodeState.idToIdxForeach(targetId) { targetIdx => - delElemBuilder += sourceIdx - delElemBuilder += lookupNow.indexOf(sourceIdx)(targetIdx) // Remove the first occurence of the sourceId/targetId combination - affectedSourceNodes += sourceIdx - } - } - } - } - val delElem = new InterleavedArrayInt(delElemBuilder.result()) - - // NestedArray.changed() parameters: - // addIdx: Int, // how many nodes are added - // addElem: InterleavedArrayInt // Array[idx -> elem] - // delElem: InterleavedArrayInt // Array[idx -> position] - // if (scala.scalajs.LinkingInfo.developmentMode) - // lookupNow = lookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) - // else - lookupNow = lookupNow.changed(changes.addIdx, addElem, delElem) - - loop(changes.addIdx) { _ => - lookupRx += Var(new Array[Int](0)) - } - - affectedSourceNodes.foreachElement { idx => - lookupRx(idx)() = lookupNow(idx).toArray - } - } -} - -final class ChildrenLayer(var lookupNow: NestedArrayInt) extends LayerState { +final class ChildrenLayer(var lookupNow: NestedArrayIntValues) extends LayerState { @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { case edge: Edge.Child => code(edge.parentId, edge.childId) case _ => } } -final class ReadLayer(var lookupNow: NestedArrayInt) extends LayerState { +final class ReadLayer(var lookupNow: NestedArrayIntValues) extends LayerState { @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { case edge: Edge.Read => code(edge.nodeId, edge.userId) case _ => diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala new file mode 100644 index 000000000..14f4a4992 --- /dev/null +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -0,0 +1,73 @@ +package wust.webApp.state.graphstate + +import rx._ +import flatland._ +import wust.ids._ +import wust.util.algorithm._ +import wust.util.collection._ +import wust.util.macros.InlineList +import wust.graph._ +import wust.util.time.time + +import scala.collection.{ breakOut, immutable, mutable } + +final case class LayerChanges( + addIdx: Int, + addEdges: Array[Edge] = Array.empty, + delEdges: Array[Edge] = Array.empty +) + +abstract class LayerState { + var lookupNow: NestedArrayIntValues + val lookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = lookupNow.map(slice => Var(slice.toArray))(breakOut) + + @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] + + def update(nodeState: NodeState, changes: LayerChanges): Unit = { + val affectedSourceNodes = new mutable.ArrayBuffer[Int] + val addElemBuilder = new mutable.ArrayBuilder.ofInt + changes.addEdges.foreach { + ifMyEdge { (sourceId, targetId) => + nodeState.idToIdxForeach(sourceId) { sourceIdx => + nodeState.idToIdxForeach(targetId) { targetIdx => + addElemBuilder += sourceIdx + addElemBuilder += targetIdx + affectedSourceNodes += sourceIdx + } + } + } + } + val addElem = new InterleavedArrayInt(addElemBuilder.result()) + + val delElemBuilder = new mutable.ArrayBuilder.ofInt + changes.delEdges.foreach { + ifMyEdge { (sourceId, targetId) => + nodeState.idToIdxForeach(sourceId) { sourceIdx => + nodeState.idToIdxForeach(targetId) { targetIdx => + delElemBuilder += sourceIdx + delElemBuilder += lookupNow.indexOf(sourceIdx)(targetIdx) // Remove the first occurence of the sourceId/targetId combination + affectedSourceNodes += sourceIdx + } + } + } + } + val delElem = new InterleavedArrayInt(delElemBuilder.result()) + + // NestedArray.changed() parameters: + // addIdx: Int, // how many nodes are added + // addElem: InterleavedArrayInt // Array[idx -> elem] + // delElem: InterleavedArrayInt // Array[idx -> position] + // if (scala.scalajs.LinkingInfo.developmentMode) + // lookupNow = lookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) + // else + lookupNow = lookupNow.changed(changes.addIdx, addElem, delElem) + + loop(changes.addIdx) { _ => + lookupRx += Var(new Array[Int](0)) + } + + affectedSourceNodes.foreachElement { idx => + lookupRx(idx)() = lookupNow(idx).toArray + } + } +} diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala new file mode 100644 index 000000000..1bee59a60 --- /dev/null +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala @@ -0,0 +1,72 @@ +package wust.webApp.state.graphstate + +import rx._ +import flatland._ +import wust.ids._ +import wust.util.algorithm._ +import wust.util.collection._ +import wust.util.macros.InlineList +import wust.graph._ +import wust.util.time.time + +import scala.collection.{ breakOut, immutable, mutable } + +object NodeState { + def apply(graphNodes: Array[Node]) = { + val nodes = mutable.ArrayBuffer.empty[Node] + val idToIdxHashMap = mutable.HashMap.empty[NodeId, Int] + idToIdxHashMap.sizeHint(graphNodes.length) + + graphNodes.foreachIndexAndElement { (idx, node) => + nodes += node + idToIdxHashMap(node.id) = idx + } + new NodeState(nodes, idToIdxHashMap) + } +} + +final class NodeState private ( + val nodesNow: mutable.ArrayBuffer[Node], + val idToIdxHashMap: mutable.HashMap[NodeId, Int] +) { + val nodesRx: mutable.ArrayBuffer[Var[Node]] = nodesNow.map(Var(_)) + + @inline def idToIdxFold[T](id: NodeId)(default: => T)(f: Int => T): T = { + idToIdxHashMap.get(id) match { + case Some(idx) => f(idx) + case None => default + } + } + @inline def idToIdxForeach[U](id: NodeId)(f: Int => U): Unit = idToIdxFold(id)(())(f(_)) + @inline def idToIdxMap[T](id: NodeId)(f: Int => T): Option[T] = idToIdxFold(id)(Option.empty[T])(idx => Some(f(idx))) + @inline def idToIdxOrThrow(nodeId: NodeId): Int = idToIdxHashMap(nodeId) + def idToIdx(nodeId: NodeId): Option[Int] = idToIdxFold[Option[Int]](nodeId)(None)(Some(_)) + def nodesByIdOrThrow(nodeId: NodeId): Node = nodesNow(idToIdxOrThrow(nodeId)) + def nodesById(nodeId: NodeId): Option[Node] = idToIdxFold[Option[Node]](nodeId)(None)(idx => Some(nodesNow(idx))) + + def update(changes: GraphChanges): LayerChanges = { + // register new and updated nodes + + var addIdx = 0 // counts the number of newly added nodes + changes.addNodes.foreachElement { node => + val nodeId = node.id + idToIdxFold(nodeId){ + // add new node and update idToIdxHashMap + val newIdx = nodesNow.length + nodesNow += node + nodesRx += Var(node) + idToIdxHashMap(nodeId) = newIdx + addIdx += 1 + }{ idx => + // already exists, update node + nodesNow(idx) = node + nodesRx(idx)() = node + } + } + + assert(nodesNow.length == idToIdxHashMap.size) + assert(nodesNow.length == nodesRx.length) + LayerChanges(addIdx, changes.addEdges, changes.delEdges) + } +} + From f4e012477a3ca0a461d8f25d4294a3164feaf8b3 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Thu, 11 Jul 2019 19:11:00 -0400 Subject: [PATCH 03/14] wip --- .../webApp/state/graphstate/EdgeState.scala | 3 + .../webApp/state/graphstate/LayerState.scala | 4 +- .../wust/webApp/state/EdgeStateSpec.scala | 55 +++++++++ .../wust/webApp/state/GraphStateSpec.scala | 108 +++++------------- .../wust/webApp/state/NodeStateSpec.scala | 77 +++++++++++++ 5 files changed, 168 insertions(+), 79 deletions(-) create mode 100644 webApp/src/test/scala/wust/webApp/state/EdgeStateSpec.scala create mode 100644 webApp/src/test/scala/wust/webApp/state/NodeStateSpec.scala diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala index 82a2ed129..e1a3e485b 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala @@ -32,6 +32,9 @@ final class EdgeState private ( val idToIdxHashMap: mutable.HashMap[(NodeId, NodeId), Int] ) { val edgesRx: mutable.ArrayBuffer[Var[Edge]] = edgesNow.map(Var(_)) + + @inline def idToIdxOrThrow(endPoints: (NodeId, NodeId)): Int = idToIdxHashMap(endPoints) + def update(changes: GraphChanges): Unit = { // register new and updated edges diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index 14f4a4992..ccacd8021 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -19,14 +19,16 @@ final case class LayerChanges( abstract class LayerState { var lookupNow: NestedArrayIntValues + // var revLookupNow: NestedArrayIntValues val lookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = lookupNow.map(slice => Var(slice.toArray))(breakOut) + // val revLookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = revLookupNow.map(slice => Var(slice.toArray))(breakOut) @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] def update(nodeState: NodeState, changes: LayerChanges): Unit = { val affectedSourceNodes = new mutable.ArrayBuffer[Int] val addElemBuilder = new mutable.ArrayBuilder.ofInt - changes.addEdges.foreach { + changes.addEdges.foreachElement { ifMyEdge { (sourceId, targetId) => nodeState.idToIdxForeach(sourceId) { sourceIdx => nodeState.idToIdxForeach(targetId) { targetIdx => diff --git a/webApp/src/test/scala/wust/webApp/state/EdgeStateSpec.scala b/webApp/src/test/scala/wust/webApp/state/EdgeStateSpec.scala new file mode 100644 index 000000000..290e2a9ba --- /dev/null +++ b/webApp/src/test/scala/wust/webApp/state/EdgeStateSpec.scala @@ -0,0 +1,55 @@ +package wust.webApp.state.graphstate + +import org.scalatest._ +import wust.graph._ +import wust.ids._ + +class EdgeStateSpec extends FreeSpec with MustMatchers { + def parent(childId:Cuid, parentId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) + def child(parentId:Cuid, childId:Cuid, deletedAt:Option[EpochMilli] = None) = Edge.Child(ParentId(NodeId(parentId)), deletedAt, ChildId(NodeId(childId))) + def user(id:Cuid) = Node.User(UserId(NodeId(id)), NodeData.User(id.toString, false, 0), NodeMeta.User) + implicit def stringToCuid(id:String):Cuid = Cuid.fromBase58String("5Q4is6Gc5NbA7T7W7PvAUw".dropRight(id.length) + id).right.get + implicit def idToNode(id: String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText("content"), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + def idToNode(id: String, content:String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText(content), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + + "factory" in { + val edges = Array[Edge](child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + val edgeState = EdgeState(edges) + + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))) == child("B", "C")) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))) == child("B", "D")) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))) == child("E", "F")) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))) == child("F", "E")) + + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))).now == child("B", "C")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))).now == child("B", "D")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))).now == child("E", "F")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))).now == child("F", "E")) + } + + "add edges" in { + val edges = Array[Edge](child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + val edgeState = EdgeState(edges) + + edgeState.update(GraphChanges(addEdges = Array(child("A", "C"), child("D", "E")))) + + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))) == child("A", "C")) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))) == child("D", "E")) + + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))).now == child("A", "C")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))).now == child("D", "E")) + } + + "update edge" in { + val edges = Array[Edge](child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + val edgeState = EdgeState(edges) + + edgeState.update(GraphChanges(addEdges = Array(child("A", "C", Some(EpochMilli(0L))), child("D", "E", Some(EpochMilli(1L)))))) + + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))) == child("A", "C", Some(EpochMilli(0L)))) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))) == child("D", "E", Some(EpochMilli(1L)))) + + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))).now == child("A", "C", Some(EpochMilli(0L)))) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))).now == child("D", "E", Some(EpochMilli(1L)))) + } +} diff --git a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala index 395672941..0d94051ba 100644 --- a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala +++ b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala @@ -1,4 +1,4 @@ -package wust.webApp.state +package wust.webApp.state.graphstate import org.scalatest._ import wust.graph._ @@ -20,87 +20,39 @@ class IncrementalReactiveGraphSpec extends FreeSpec with MustMatchers { val graphState = new GraphState(graph) import graphState.{nodeState, children} - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("B":Cuid))) == idToNode("B")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("C":Cuid))) == idToNode("C")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("D":Cuid))) == idToNode("D")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("E":Cuid))) == idToNode("E")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("F":Cuid))) == idToNode("F")) - - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now == idToNode("B")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now == idToNode("C")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now == idToNode("D")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now == idToNode("E")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now == idToNode("F")) - - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List()) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C", "D")) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("F")) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("E")) - - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C", "D")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("F")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("E")) - } - "GraphState add nodes" in { - val graph = Graph( - nodes = Array("A", "B", "C", "D", "E", "F"), - edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) - ) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) - val graphState = new GraphState(graph) - graphState.update(GraphChanges(addNodes = Array[Node]("G", "H"))) - import graphState.{nodeState, children} - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("B":Cuid))) == idToNode("B")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("C":Cuid))) == idToNode("C")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("D":Cuid))) == idToNode("D")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("E":Cuid))) == idToNode("E")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("F":Cuid))) == idToNode("F")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("G":Cuid))) == idToNode("G")) - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("H":Cuid))) == idToNode("H")) - - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now == idToNode("B")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now == idToNode("C")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now == idToNode("D")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now == idToNode("E")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now == idToNode("F")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now == idToNode("G")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("H":Cuid))).now == idToNode("H")) - - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C", "D")) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("F")) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("E")) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) - - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C", "D")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("F")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("E")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) } - "GraphState update node" in { + "GraphState add nodes" in { val graph = Graph( nodes = Array("A", "B", "C", "D", "E", "F"), edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) ) val graphState = new GraphState(graph) + graphState.update(GraphChanges(addNodes = Array[Node]("G", "H"))) import graphState.{nodeState, children} - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A")) - - graphState.update(GraphChanges(addNodes = Array[Node](idToNode("A", "changed")))) - - assert(nodeState.nodes(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A", "changed")) - assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A", "changed")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) } "GraphState add edge" in { @@ -112,13 +64,13 @@ class IncrementalReactiveGraphSpec extends FreeSpec with MustMatchers { val graphState = new GraphState(graph) import graphState.{nodeState, children} - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) graphState.update(GraphChanges(addEdges = Array(child("A", "C")))) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("C")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C")) } "GraphState add nodes with edge" in { @@ -131,8 +83,8 @@ class IncrementalReactiveGraphSpec extends FreeSpec with MustMatchers { import graphState.{nodeState, children} graphState.update(GraphChanges(addNodes = Array("G","H"), addEdges = Array(child("G", "H")))) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("H")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]("H")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("H")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("H")) } "GraphState delete edge" in { @@ -145,8 +97,8 @@ class IncrementalReactiveGraphSpec extends FreeSpec with MustMatchers { import graphState.{nodeState, children} graphState.update(GraphChanges(delEdges = Array(child("E", "F")))) - assert(children.lookup(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodes(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) } //TODO: delete for author LabeledProperty edges, where edges can have the same source/target combination diff --git a/webApp/src/test/scala/wust/webApp/state/NodeStateSpec.scala b/webApp/src/test/scala/wust/webApp/state/NodeStateSpec.scala new file mode 100644 index 000000000..2bae9702c --- /dev/null +++ b/webApp/src/test/scala/wust/webApp/state/NodeStateSpec.scala @@ -0,0 +1,77 @@ +package wust.webApp.state.graphstate + +import org.scalatest._ +import wust.graph._ +import wust.ids._ + +class NodeStateSpec extends FreeSpec with MustMatchers { + def parent(childId:Cuid, parentId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) + def child(parentId:Cuid, childId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) + def user(id:Cuid) = Node.User(UserId(NodeId(id)), NodeData.User(id.toString, false, 0), NodeMeta.User) + implicit def stringToCuid(id:String):Cuid = Cuid.fromBase58String("5Q4is6Gc5NbA7T7W7PvAUw".dropRight(id.length) + id).right.get + implicit def idToNode(id: String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText("content"), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + def idToNode(id: String, content:String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText(content), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + + "factory" in { + val nodes = Array[Node]("A", "B", "C") + val nodeState = NodeState(nodes) + + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A")) + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))) == idToNode("B")) + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))) == idToNode("C")) + + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now == idToNode("B")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now == idToNode("C")) + } + + "add nodes" in { + val nodes = Array[Node]("A", "B", "C") + val nodeState = NodeState(nodes) + + nodeState.update(GraphChanges(addNodes = Array("G","H"))) + + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A")) + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))) == idToNode("B")) + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))) == idToNode("C")) + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))) == idToNode("G")) + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("H":Cuid))) == idToNode("H")) + + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now == idToNode("B")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now == idToNode("C")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now == idToNode("G")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("H":Cuid))).now == idToNode("H")) + } + + + "update nodes" in { + val nodes = Array[Node]("A", "B", "C") + val nodeState = NodeState(nodes) + + nodeState.update(GraphChanges(addNodes = Array[Node](idToNode("A", "changed")))) + + assert(nodeState.nodesNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))) == idToNode("A", "changed")) + assert(nodeState.nodesRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now == idToNode("A", "changed")) + } + + "update: return LayerChanges" in { + val nodes = Array[Node]("A", "B", "C") + + val nodeState = NodeState(nodes) + val layerChanges = nodeState.update(GraphChanges( + addNodes = Array("G","H"), + addEdges = Array(child("G", "H")), + delEdges = Array(child("X", "Y")) + )) + + val expected = LayerChanges( + addIdx = 2, + addEdges = Array(child("G", "H")), + delEdges = Array(child("X", "Y")) + ) + assert(layerChanges.addIdx == expected.addIdx) + assert(layerChanges.addEdges.toList == expected.addEdges.toList) + assert(layerChanges.delEdges.toList == expected.delEdges.toList) + } +} From 32336da90afc676c865f89c4160b5973ff62db7c Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Thu, 11 Jul 2019 21:27:37 -0400 Subject: [PATCH 04/14] wip --- .../webApp/state/graphstate/EdgeState.scala | 27 +- .../webApp/state/graphstate/GraphState.scala | 359 ++++++++---------- .../webApp/state/graphstate/LayerState.scala | 9 +- .../webApp/state/graphstate/NodeState.scala | 23 +- .../wust/webApp/state/GraphStateSpec.scala | 2 +- 5 files changed, 191 insertions(+), 229 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala index e1a3e485b..eca44a2ef 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala @@ -12,26 +12,19 @@ import wust.util.time.time import scala.collection.{ breakOut, immutable, mutable } object EdgeState { - def apply(graphEdges: Array[Edge]) = { - val edges = mutable.ArrayBuffer.empty[Edge] - val idToIdxHashMap = mutable.HashMap.empty[(NodeId, NodeId), Int] - idToIdxHashMap.sizeHint(graphEdges.length) - - graphEdges.foreachIndexAndElement { (idx, edge) => - edges += edge - idToIdxHashMap(edgeKey(edge)) = idx - } - new EdgeState(edges, idToIdxHashMap) - } - @inline def edgeKey(edge: Edge): (NodeId, NodeId) = edge.sourceId -> edge.targetId + + def apply(edges: Array[Edge]):EdgeState = { + val edgeState = new EdgeState + edgeState.update(GraphChanges(addEdges = edges)) + edgeState + } } -final class EdgeState private ( - val edgesNow: mutable.ArrayBuffer[Edge], - val idToIdxHashMap: mutable.HashMap[(NodeId, NodeId), Int] -) { - val edgesRx: mutable.ArrayBuffer[Var[Edge]] = edgesNow.map(Var(_)) +final class EdgeState { + val edgesNow: mutable.ArrayBuffer[Edge] = mutable.ArrayBuffer.empty + val idToIdxHashMap: mutable.HashMap[(NodeId, NodeId), Int] = mutable.HashMap.empty + val edgesRx: mutable.ArrayBuffer[Var[Edge]] = mutable.ArrayBuffer.empty @inline def idToIdxOrThrow(endPoints: (NodeId, NodeId)): Int = idToIdxHashMap(endPoints) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index 028adc030..06f0f6c30 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -16,13 +16,14 @@ import scala.collection.{ breakOut, immutable, mutable } final class GraphState(initialGraph: Graph) { import initialGraph.{ nodes, edges } - val nodeState = NodeState(initialGraph.nodes) - val edgeState = EdgeState(initialGraph.edges) - import nodeState.idToIdxForeach + val nodeState = new NodeState + val edgeState = new EdgeState + // import nodeState.idToIdxForeach - val n = initialGraph.size - private val consistentEdges = ArraySet.create(edges.length) - val edgesIdx = InterleavedArrayInt.create(edges.length) + + // val n = initialGraph.size + // private val consistentEdges = ArraySet.create(edges.length) + // val edgesIdx = InterleavedArrayInt.create(edges.length) // TODO: have one big triple nested array for all edge lookups? @@ -32,8 +33,8 @@ final class GraphState(initialGraph: Graph) { // private val outDegree = new Array[Int](n) // private val parentsDegree = new Array[Int](n) // private val contentsDegree = new Array[Int](n) - private val readDegree = new Array[Int](n) - private val childrenDegree = new Array[Int](n) + // private val readDegree = new Array[Int](n) + // private val childrenDegree = new Array[Int](n) // private val messageChildrenDegree = new Array[Int](n) // private val taskChildrenDegree = new Array[Int](n) // private val noteChildrenDegree = new Array[Int](n) @@ -57,92 +58,92 @@ final class GraphState(initialGraph: Graph) { // private val automatedReverseDegree = new Array[Int](n) // private val derivedFromTemplateDegree = new Array[Int](n) - private val buildNow = EpochMilli.now + // private val buildNow = EpochMilli.now - edges.foreachIndexAndElement { (edgeIdx, edge) => - idToIdxForeach(edge.sourceId) { sourceIdx => - idToIdxForeach(edge.targetId) { targetIdx => - consistentEdges.add(edgeIdx) - edgesIdx.updatea(edgeIdx, sourceIdx) - edgesIdx.updateb(edgeIdx, targetIdx) - // outDegree(sourceIdx) += 1 - // edge match { - // case e: Edge.Content => contentsDegree(sourceIdx) += 1 - // case _ => - // } + // edges.foreachIndexAndElement { (edgeIdx, edge) => + // idToIdxForeach(edge.sourceId) { sourceIdx => + // idToIdxForeach(edge.targetId) { targetIdx => + // consistentEdges.add(edgeIdx) + // edgesIdx.updatea(edgeIdx, sourceIdx) + // edgesIdx.updateb(edgeIdx, targetIdx) + // // outDegree(sourceIdx) += 1 + // // edge match { + // // case e: Edge.Content => contentsDegree(sourceIdx) += 1 + // // case _ => + // // } - edge match { - // case _: Edge.Author => - // authorshipDegree(sourceIdx) += 1 - // case _: Edge.Member => - // membershipsForNodeDegree(sourceIdx) += 1 - case e: Edge.Child => - // val childIsMessage = nodes(targetIdx).role == NodeRole.Message - // val childIsTask = nodes(targetIdx).role == NodeRole.Task - // val childIsNote = nodes(targetIdx).role == NodeRole.Note - // val childIsProject = nodes(targetIdx).role == NodeRole.Project - // val childIsTag = nodes(targetIdx).role == NodeRole.Tag - // val parentIsTag = nodes(sourceIdx).role == NodeRole.Tag - // val parentIsStage = nodes(sourceIdx).role == NodeRole.Stage - // parentsDegree(targetIdx) += 1 - childrenDegree(sourceIdx) += 1 + // edge match { + // // case _: Edge.Author => + // // authorshipDegree(sourceIdx) += 1 + // // case _: Edge.Member => + // // membershipsForNodeDegree(sourceIdx) += 1 + // case e: Edge.Child => + // // val childIsMessage = nodes(targetIdx).role == NodeRole.Message + // // val childIsTask = nodes(targetIdx).role == NodeRole.Task + // // val childIsNote = nodes(targetIdx).role == NodeRole.Note + // // val childIsProject = nodes(targetIdx).role == NodeRole.Project + // // val childIsTag = nodes(targetIdx).role == NodeRole.Tag + // // val parentIsTag = nodes(sourceIdx).role == NodeRole.Tag + // // val parentIsStage = nodes(sourceIdx).role == NodeRole.Stage + // // parentsDegree(targetIdx) += 1 + // childrenDegree(sourceIdx) += 1 - // if (childIsProject) projectChildrenDegree(sourceIdx) += 1 - // if (childIsMessage) messageChildrenDegree(sourceIdx) += 1 - // if (childIsTask) taskChildrenDegree(sourceIdx) += 1 - // if (childIsNote) noteChildrenDegree(sourceIdx) += 1 + // // if (childIsProject) projectChildrenDegree(sourceIdx) += 1 + // // if (childIsMessage) messageChildrenDegree(sourceIdx) += 1 + // // if (childIsTask) taskChildrenDegree(sourceIdx) += 1 + // // if (childIsNote) noteChildrenDegree(sourceIdx) += 1 - // e.data.deletedAt match { - // case None => - // if (childIsTag) tagChildrenDegree(sourceIdx) += 1 - // if (parentIsTag) tagParentsDegree(targetIdx) += 1 - // if (parentIsStage) stageParentsDegree(targetIdx) += 1 - // notDeletedParentsDegree(targetIdx) += 1 - // notDeletedChildrenDegree(sourceIdx) += 1 - // case Some(deletedAt) => - // if (deletedAt isAfter buildNow) { // in the future - // if (childIsTag) tagChildrenDegree(sourceIdx) += 1 - // if (parentIsTag) tagParentsDegree(targetIdx) += 1 - // if (parentIsStage) stageParentsDegree(targetIdx) += 1 - // notDeletedParentsDegree(targetIdx) += 1 - // notDeletedChildrenDegree(sourceIdx) += 1 - // } - // // TODO everything deleted further in the past should already be filtered in backend - // // BUT received on request - // } - // case _: Edge.Assigned => - // assignedNodesDegree(targetIdx) += 1 - // assignedUsersDegree(sourceIdx) += 1 - // case _: Edge.Expanded => - // expandedEdgesDegree(sourceIdx) += 1 - // case _: Edge.Notify => - // notifyByUserDegree(targetIdx) += 1 - // case _: Edge.Pinned => - // pinnedNodeDegree(targetIdx) += 1 - // case _: Edge.Invite => - // inviteNodeDegree(targetIdx) += 1 - // case _: Edge.LabeledProperty => - // propertiesDegree(sourceIdx) += 1 - // propertiesReverseDegree(targetIdx) += 1 - // case _: Edge.Automated => - // automatedDegree(sourceIdx) += 1 - // automatedReverseDegree(targetIdx) += 1 - // case _: Edge.DerivedFromTemplate => - // derivedFromTemplateDegree(sourceIdx) += 1 - case _: Edge.Read => - readDegree(sourceIdx) += 1 - case _ => - } - } - } - } + // // e.data.deletedAt match { + // // case None => + // // if (childIsTag) tagChildrenDegree(sourceIdx) += 1 + // // if (parentIsTag) tagParentsDegree(targetIdx) += 1 + // // if (parentIsStage) stageParentsDegree(targetIdx) += 1 + // // notDeletedParentsDegree(targetIdx) += 1 + // // notDeletedChildrenDegree(sourceIdx) += 1 + // // case Some(deletedAt) => + // // if (deletedAt isAfter buildNow) { // in the future + // // if (childIsTag) tagChildrenDegree(sourceIdx) += 1 + // // if (parentIsTag) tagParentsDegree(targetIdx) += 1 + // // if (parentIsStage) stageParentsDegree(targetIdx) += 1 + // // notDeletedParentsDegree(targetIdx) += 1 + // // notDeletedChildrenDegree(sourceIdx) += 1 + // // } + // // // TODO everything deleted further in the past should already be filtered in backend + // // // BUT received on request + // // } + // // case _: Edge.Assigned => + // // assignedNodesDegree(targetIdx) += 1 + // // assignedUsersDegree(sourceIdx) += 1 + // // case _: Edge.Expanded => + // // expandedEdgesDegree(sourceIdx) += 1 + // // case _: Edge.Notify => + // // notifyByUserDegree(targetIdx) += 1 + // // case _: Edge.Pinned => + // // pinnedNodeDegree(targetIdx) += 1 + // // case _: Edge.Invite => + // // inviteNodeDegree(targetIdx) += 1 + // // case _: Edge.LabeledProperty => + // // propertiesDegree(sourceIdx) += 1 + // // propertiesReverseDegree(targetIdx) += 1 + // // case _: Edge.Automated => + // // automatedDegree(sourceIdx) += 1 + // // automatedReverseDegree(targetIdx) += 1 + // // case _: Edge.DerivedFromTemplate => + // // derivedFromTemplateDegree(sourceIdx) += 1 + // case _: Edge.Read => + // readDegree(sourceIdx) += 1 + // case _ => + // } + // } + // } + // } // private val outgoingEdgeIdxBuilder = NestedArrayInt.builder(outDegree) // private val parentsIdxBuilder = NestedArrayInt.builder(parentsDegree) // private val parentEdgeIdxBuilder = NestedArrayInt.builder(parentsDegree) // private val contentsEdgeIdxBuilder = NestedArrayInt.builder(contentsDegree) - private val readEdgeIdxBuilder = NestedArrayInt.builder(readDegree) - private val childrenIdxBuilder = NestedArrayInt.builder(childrenDegree) + // private val readEdgeIdxBuilder = NestedArrayInt.builder(readDegree) + // private val childrenIdxBuilder = NestedArrayInt.builder(childrenDegree) // private val childEdgeIdxBuilder = NestedArrayInt.builder(childrenDegree) // private val messageChildrenIdxBuilder = NestedArrayInt.builder(messageChildrenDegree) // private val taskChildrenIdxBuilder = NestedArrayInt.builder(taskChildrenDegree) @@ -168,90 +169,90 @@ final class GraphState(initialGraph: Graph) { // private val automatedEdgeReverseIdxBuilder = NestedArrayInt.builder(automatedReverseDegree) // private val derivedFromTemplateEdgeIdxBuilder = NestedArrayInt.builder(derivedFromTemplateDegree) - consistentEdges.foreach { edgeIdx => - val sourceIdx = edgesIdx.a(edgeIdx) - val targetIdx = edgesIdx.b(edgeIdx) - val edge = edges(edgeIdx) - // outgoingEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // consistentEdges.foreach { edgeIdx => + // val sourceIdx = edgesIdx.a(edgeIdx) + // val targetIdx = edgesIdx.b(edgeIdx) + // val edge = edges(edgeIdx) + // // outgoingEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // edge match { - // case e: Edge.Content => contentsEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // // edge match { + // // case e: Edge.Content => contentsEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // case _ => - // } + // // case _ => + // // } - edge match { - // case _: Edge.Author => - // authorshipEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // authorIdxBuilder.add(sourceIdx, targetIdx) - // case _: Edge.Member => - // membershipEdgeForNodeIdxBuilder.add(sourceIdx, edgeIdx) - case e: Edge.Child => - // val childIsMessage = nodes(targetIdx).role == NodeRole.Message - // val childIsTask = nodes(targetIdx).role == NodeRole.Task - // val childIsNote = nodes(targetIdx).role == NodeRole.Note - // val childIsTag = nodes(targetIdx).role == NodeRole.Tag - // val childIsProject = nodes(targetIdx).role == NodeRole.Project - // val parentIsTag = nodes(sourceIdx).role == NodeRole.Tag - // val parentIsStage = nodes(sourceIdx).role == NodeRole.Stage - // parentsIdxBuilder.add(targetIdx, sourceIdx) - // parentEdgeIdxBuilder.add(targetIdx, edgeIdx) - childrenIdxBuilder.add(sourceIdx, targetIdx) - // childEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // edge match { + // // case _: Edge.Author => + // // authorshipEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // // authorIdxBuilder.add(sourceIdx, targetIdx) + // // case _: Edge.Member => + // // membershipEdgeForNodeIdxBuilder.add(sourceIdx, edgeIdx) + // case e: Edge.Child => + // // val childIsMessage = nodes(targetIdx).role == NodeRole.Message + // // val childIsTask = nodes(targetIdx).role == NodeRole.Task + // // val childIsNote = nodes(targetIdx).role == NodeRole.Note + // // val childIsTag = nodes(targetIdx).role == NodeRole.Tag + // // val childIsProject = nodes(targetIdx).role == NodeRole.Project + // // val parentIsTag = nodes(sourceIdx).role == NodeRole.Tag + // // val parentIsStage = nodes(sourceIdx).role == NodeRole.Stage + // // parentsIdxBuilder.add(targetIdx, sourceIdx) + // // parentEdgeIdxBuilder.add(targetIdx, edgeIdx) + // childrenIdxBuilder.add(sourceIdx, targetIdx) + // // childEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // if (childIsProject) projectChildrenIdxBuilder.add(sourceIdx, targetIdx) - // if (childIsMessage) messageChildrenIdxBuilder.add(sourceIdx, targetIdx) - // if (childIsTask) taskChildrenIdxBuilder.add(sourceIdx, targetIdx) - // if (childIsNote) noteChildrenIdxBuilder.add(sourceIdx, targetIdx) + // // if (childIsProject) projectChildrenIdxBuilder.add(sourceIdx, targetIdx) + // // if (childIsMessage) messageChildrenIdxBuilder.add(sourceIdx, targetIdx) + // // if (childIsTask) taskChildrenIdxBuilder.add(sourceIdx, targetIdx) + // // if (childIsNote) noteChildrenIdxBuilder.add(sourceIdx, targetIdx) - // e.data.deletedAt match { - // case None => - // if (childIsTag) tagChildrenIdxBuilder.add(sourceIdx, targetIdx) - // if (parentIsTag) tagParentsIdxBuilder.add(targetIdx, sourceIdx) - // if (parentIsStage) stageParentsIdxBuilder.add(targetIdx, sourceIdx) - // notDeletedParentsIdxBuilder.add(targetIdx, sourceIdx) - // notDeletedChildrenIdxBuilder.add(sourceIdx, targetIdx) - // case Some(deletedAt) => - // if (deletedAt isAfter buildNow) { // in the future - // if (childIsTag) tagChildrenIdxBuilder.add(sourceIdx, targetIdx) - // if (parentIsTag) tagParentsIdxBuilder.add(targetIdx, sourceIdx) - // if (parentIsStage) stageParentsIdxBuilder.add(targetIdx, sourceIdx) - // notDeletedParentsIdxBuilder.add(targetIdx, sourceIdx) - // notDeletedChildrenIdxBuilder.add(sourceIdx, targetIdx) - // } - // // TODO everything deleted further in the past should already be filtered in backend - // // BUT received on request - // } - // case _: Edge.Expanded => - // expandedEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // case _: Edge.Assigned => - // assignedNodesIdxBuilder.add(targetIdx, sourceIdx) - // assignedUsersIdxBuilder.add(sourceIdx, targetIdx) - // case _: Edge.Notify => - // notifyByUserIdxBuilder.add(targetIdx, sourceIdx) - // case _: Edge.Pinned => - // pinnedNodeIdxBuilder.add(targetIdx, sourceIdx) - // case _: Edge.Invite => - // inviteNodeIdxBuilder.add(targetIdx, sourceIdx) - // case _: Edge.LabeledProperty => - // propertiesEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // propertiesEdgeReverseIdxBuilder.add(targetIdx, edgeIdx) - // case _: Edge.Automated => - // automatedEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // automatedEdgeReverseIdxBuilder.add(targetIdx, edgeIdx) - // case _: Edge.DerivedFromTemplate => - // derivedFromTemplateEdgeIdxBuilder.add(sourceIdx, edgeIdx) - case _: Edge.Read => - readEdgeIdxBuilder.add(sourceIdx, edgeIdx) - case _ => - } - } + // // e.data.deletedAt match { + // // case None => + // // if (childIsTag) tagChildrenIdxBuilder.add(sourceIdx, targetIdx) + // // if (parentIsTag) tagParentsIdxBuilder.add(targetIdx, sourceIdx) + // // if (parentIsStage) stageParentsIdxBuilder.add(targetIdx, sourceIdx) + // // notDeletedParentsIdxBuilder.add(targetIdx, sourceIdx) + // // notDeletedChildrenIdxBuilder.add(sourceIdx, targetIdx) + // // case Some(deletedAt) => + // // if (deletedAt isAfter buildNow) { // in the future + // // if (childIsTag) tagChildrenIdxBuilder.add(sourceIdx, targetIdx) + // // if (parentIsTag) tagParentsIdxBuilder.add(targetIdx, sourceIdx) + // // if (parentIsStage) stageParentsIdxBuilder.add(targetIdx, sourceIdx) + // // notDeletedParentsIdxBuilder.add(targetIdx, sourceIdx) + // // notDeletedChildrenIdxBuilder.add(sourceIdx, targetIdx) + // // } + // // // TODO everything deleted further in the past should already be filtered in backend + // // // BUT received on request + // // } + // // case _: Edge.Expanded => + // // expandedEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // // case _: Edge.Assigned => + // // assignedNodesIdxBuilder.add(targetIdx, sourceIdx) + // // assignedUsersIdxBuilder.add(sourceIdx, targetIdx) + // // case _: Edge.Notify => + // // notifyByUserIdxBuilder.add(targetIdx, sourceIdx) + // // case _: Edge.Pinned => + // // pinnedNodeIdxBuilder.add(targetIdx, sourceIdx) + // // case _: Edge.Invite => + // // inviteNodeIdxBuilder.add(targetIdx, sourceIdx) + // // case _: Edge.LabeledProperty => + // // propertiesEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // // propertiesEdgeReverseIdxBuilder.add(targetIdx, edgeIdx) + // // case _: Edge.Automated => + // // automatedEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // // automatedEdgeReverseIdxBuilder.add(targetIdx, edgeIdx) + // // case _: Edge.DerivedFromTemplate => + // // derivedFromTemplateEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // case _: Edge.Read => + // readEdgeIdxBuilder.add(sourceIdx, edgeIdx) + // case _ => + // } + // } // val outgoingEdgeIdx: NestedArrayInt = outgoingEdgeIdxBuilder.result() // val parentsIdx: NestedArrayInt = parentsIdxBuilder.result() // val parentEdgeIdx: NestedArrayInt = parentEdgeIdxBuilder.result() - val readEdgeIdx: NestedArrayIntValues = readEdgeIdxBuilder.result() - val childrenIdx: NestedArrayIntValues = childrenIdxBuilder.result() + // val readEdgeIdx: NestedArrayIntValues = readEdgeIdxBuilder.result() + // val childrenIdx: NestedArrayIntValues = childrenIdxBuilder.result() // val childEdgeIdx: NestedArrayInt = childEdgeIdxBuilder.result() // val contentsEdgeIdx: NestedArrayInt = contentsEdgeIdxBuilder.result() // val messageChildrenIdx: NestedArrayInt = messageChildrenIdxBuilder.result() @@ -277,38 +278,12 @@ final class GraphState(initialGraph: Graph) { // val automatedEdgeIdx: NestedArrayInt = automatedEdgeIdxBuilder.result() // val automatedEdgeReverseIdx: NestedArrayInt = automatedEdgeReverseIdxBuilder.result() // val derivedFromTemplateEdgeIdx: NestedArrayInt = derivedFromTemplateEdgeIdxBuilder.result() - val children = new ChildrenLayer(childrenIdx) - val read = new ChildrenLayer(childrenIdx) - // private val sortedAuthorshipEdgeIdx: NestedArrayInt = NestedArrayInt(authorshipEdgeIdx.map(slice => slice.sortBy(author => edges(author).as[Edge.Author].data.timestamp).toArray)(breakOut) : Array[Array[Int]]) - // private val (nodeCreated: mutable.ArrayBuffer[Var[EpochMilli]], nodeCreatorIdx: mutable.ArrayBuffer[Int], nodeModified: mutable.ArrayBuffer[Var[EpochMilli]]) = { - // val nodeCreator = new mutable.ArrayBuffer[Int](n) - // val nodeCreated = new mutable.ArrayBuffer.fill[Var[EpochMilli]](n)(Var(EpochMilli.min)) // filled with 0L = EpochMilli.min by default - // val nodeModified = new mutable.ArrayBuffer.fill[Var[EpochMilli]](n)(Var(EpochMilli.min)) // filled with 0L = EpochMilli.min by default - // var nodeIdx = 0 - // while (nodeIdx < n) { - // val authorEdgeIndices: ArraySliceInt = sortedAuthorshipEdgeIdx(nodeIdx) - // if (authorEdgeIndices.nonEmpty) { - // val (createdEdgeIdx, lastModifierEdgeIdx) = (authorEdgeIndices.head, authorEdgeIndices.last) - // nodeCreated(nodeIdx) = edges(createdEdgeIdx).as[Edge.Author].data.timestamp - // nodeCreator(nodeIdx) = edgesIdx.b(createdEdgeIdx) - // nodeModified(nodeIdx) = edges(lastModifierEdgeIdx).as[Edge.Author].data.timestamp - // } else { - // nodeCreator(nodeIdx) = -1 //TODO: we do not want -1 indices... - // } - // nodeIdx += 1 - // } - // (nodeCreated, nodeCreator, nodeModified) - // } - // def nodeDeepCreated(nodeIdx:Int):Rx[EpochMilli] = { - // var created = nodeCreated(nodeIdx) - // dfs.foreach(_(nodeIdx), dfs.withoutStart, childrenIdx, { childIdx => - // val childCreated = nodeCreated(childIdx) - // if(childCreated isAfter created) created = childCreated - // }) - // created - // } + val children = new ChildrenLayer + val read = new ChildrenLayer + + update(GraphChanges(addNodes = initialGraph.nodes, addEdges = initialGraph.edges)) def update(changes: GraphChanges) = { time("graphstate") { @@ -321,14 +296,14 @@ final class GraphState(initialGraph: Graph) { } -final class ChildrenLayer(var lookupNow: NestedArrayIntValues) extends LayerState { +final class ChildrenLayer extends LayerState { @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { case edge: Edge.Child => code(edge.parentId, edge.childId) case _ => } } -final class ReadLayer(var lookupNow: NestedArrayIntValues) extends LayerState { +final class ReadLayer extends LayerState { @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { case edge: Edge.Read => code(edge.nodeId, edge.userId) case _ => diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index ccacd8021..a552e175d 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -18,10 +18,11 @@ final case class LayerChanges( ) abstract class LayerState { - var lookupNow: NestedArrayIntValues - // var revLookupNow: NestedArrayIntValues - val lookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = lookupNow.map(slice => Var(slice.toArray))(breakOut) - // val revLookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = revLookupNow.map(slice => Var(slice.toArray))(breakOut) + var lookupNow: NestedArrayIntValues = NestedArrayInt.empty + var revLookupNow: NestedArrayIntValues = NestedArrayInt.empty + val lookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty + val revLookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty + @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala index 1bee59a60..80fa6ac6a 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala @@ -12,24 +12,17 @@ import wust.util.time.time import scala.collection.{ breakOut, immutable, mutable } object NodeState { - def apply(graphNodes: Array[Node]) = { - val nodes = mutable.ArrayBuffer.empty[Node] - val idToIdxHashMap = mutable.HashMap.empty[NodeId, Int] - idToIdxHashMap.sizeHint(graphNodes.length) - - graphNodes.foreachIndexAndElement { (idx, node) => - nodes += node - idToIdxHashMap(node.id) = idx - } - new NodeState(nodes, idToIdxHashMap) + def apply(nodes: Array[Node]):NodeState = { + val nodeState = new NodeState + nodeState.update(GraphChanges(addNodes = nodes)) + nodeState } } -final class NodeState private ( - val nodesNow: mutable.ArrayBuffer[Node], - val idToIdxHashMap: mutable.HashMap[NodeId, Int] -) { - val nodesRx: mutable.ArrayBuffer[Var[Node]] = nodesNow.map(Var(_)) +final class NodeState { + val nodesNow: mutable.ArrayBuffer[Node] = mutable.ArrayBuffer.empty + val idToIdxHashMap: mutable.HashMap[NodeId, Int] = mutable.HashMap.empty + val nodesRx: mutable.ArrayBuffer[Var[Node]] = mutable.ArrayBuffer.empty @inline def idToIdxFold[T](id: NodeId)(default: => T)(f: Int => T): T = { idToIdxHashMap.get(id) match { diff --git a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala index 0d94051ba..8d720d5b7 100644 --- a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala +++ b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala @@ -4,7 +4,7 @@ import org.scalatest._ import wust.graph._ import wust.ids._ -class IncrementalReactiveGraphSpec extends FreeSpec with MustMatchers { +class GraphStateSpec extends FreeSpec with MustMatchers { def parent(childId:Cuid, parentId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) def child(parentId:Cuid, childId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) def user(id:Cuid) = Node.User(UserId(NodeId(id)), NodeData.User(id.toString, false, 0), NodeMeta.User) From 21a0c2fe734b8b43e6a0b14fba6c779aaa398f58 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Thu, 11 Jul 2019 23:34:46 -0400 Subject: [PATCH 05/14] revlookup --- .../webApp/state/graphstate/GraphState.scala | 16 ++--- .../webApp/state/graphstate/LayerState.scala | 39 ++++++++---- .../wust/webApp/state/GraphStateSpec.scala | 62 ++++++++++++++++++- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index 06f0f6c30..dc69ddbbe 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -281,7 +281,7 @@ final class GraphState(initialGraph: Graph) { val children = new ChildrenLayer - val read = new ChildrenLayer + // val read = new ChildrenLayer update(GraphChanges(addNodes = initialGraph.nodes, addEdges = initialGraph.edges)) @@ -290,7 +290,7 @@ final class GraphState(initialGraph: Graph) { edgeState.update(changes) val layerChanges = nodeState.update(changes) children.update(nodeState, layerChanges) - read.update(nodeState, layerChanges) + // read.update(nodeState, layerChanges) } } } @@ -303,9 +303,9 @@ final class ChildrenLayer extends LayerState { } } -final class ReadLayer extends LayerState { - @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { - case edge: Edge.Read => code(edge.nodeId, edge.userId) - case _ => - } -} +// final class ReadLayer extends LayerState { +// @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { +// case edge: Edge.Read => code(edge.nodeId, edge.userId) +// case _ => +// } +// } diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index a552e175d..62ef62cbc 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -28,49 +28,64 @@ abstract class LayerState { def update(nodeState: NodeState, changes: LayerChanges): Unit = { val affectedSourceNodes = new mutable.ArrayBuffer[Int] - val addElemBuilder = new mutable.ArrayBuilder.ofInt + val affectedTargetNodes = new mutable.ArrayBuffer[Int] + val addElemBuilder = new mutable.ArrayBuilder.ofRef[(Int,Int)] + val addRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int,Int)] changes.addEdges.foreachElement { ifMyEdge { (sourceId, targetId) => nodeState.idToIdxForeach(sourceId) { sourceIdx => nodeState.idToIdxForeach(targetId) { targetIdx => - addElemBuilder += sourceIdx - addElemBuilder += targetIdx + addElemBuilder += sourceIdx -> targetIdx + addRevElemBuilder += targetIdx -> sourceIdx affectedSourceNodes += sourceIdx + affectedTargetNodes += targetIdx } } } } - val addElem = new InterleavedArrayInt(addElemBuilder.result()) + val addElem = InterleavedArrayInt(addElemBuilder.result().sortBy(_._1)) //TODO: performance: build InterleavedArray directly to avoid tuples and reiteration in InterleavedArrayInt.apply, but how to sort? Put two ints into one long? + val addRevElem = InterleavedArrayInt(addRevElemBuilder.result().sortBy(_._1)) - val delElemBuilder = new mutable.ArrayBuilder.ofInt + val delElemBuilder = new mutable.ArrayBuilder.ofRef[(Int,Int)] + val delRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int,Int)] changes.delEdges.foreach { ifMyEdge { (sourceId, targetId) => nodeState.idToIdxForeach(sourceId) { sourceIdx => nodeState.idToIdxForeach(targetId) { targetIdx => - delElemBuilder += sourceIdx - delElemBuilder += lookupNow.indexOf(sourceIdx)(targetIdx) // Remove the first occurence of the sourceId/targetId combination + delElemBuilder += sourceIdx -> lookupNow.indexOf(sourceIdx)(targetIdx) // Remove the first occurence of the sourceId/targetId combination + delRevElemBuilder += targetIdx -> lookupNow.indexOf(targetIdx)(sourceIdx) // Remove the first occurence of the sourceId/targetId combination affectedSourceNodes += sourceIdx + affectedTargetNodes += targetIdx } } } } - val delElem = new InterleavedArrayInt(delElemBuilder.result()) + val delElem = InterleavedArrayInt(delElemBuilder.result().sortBy(_._1)) + val delRevElem = InterleavedArrayInt(delRevElemBuilder.result().sortBy(_._1)) // NestedArray.changed() parameters: // addIdx: Int, // how many nodes are added // addElem: InterleavedArrayInt // Array[idx -> elem] // delElem: InterleavedArrayInt // Array[idx -> position] - // if (scala.scalajs.LinkingInfo.developmentMode) - // lookupNow = lookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) - // else - lookupNow = lookupNow.changed(changes.addIdx, addElem, delElem) + if (scala.scalajs.LinkingInfo.developmentMode) { + lookupNow = lookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) + revLookupNow = revLookupNow.changedWithAssertions(changes.addIdx, addRevElem, delRevElem) + } + else { + lookupNow = lookupNow.changed(changes.addIdx, addElem, delElem) + revLookupNow = revLookupNow.changed(changes.addIdx, addRevElem, delRevElem) + } loop(changes.addIdx) { _ => lookupRx += Var(new Array[Int](0)) + revLookupRx += Var(new Array[Int](0)) } affectedSourceNodes.foreachElement { idx => lookupRx(idx)() = lookupNow(idx).toArray } + affectedTargetNodes.foreachElement { idx => + revLookupRx(idx)() = revLookupNow(idx).toArray + } } } diff --git a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala index 8d720d5b7..a465aec77 100644 --- a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala +++ b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala @@ -15,21 +15,41 @@ class GraphStateSpec extends FreeSpec with MustMatchers { "GraphState factory" in { val graph = Graph( nodes = Array("A", "B", "C", "D", "E", "F"), - edges = Array(child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) + edges = Array(child("B", "C"), child("B", "D"), child("F", "E"), child("E", "F")) ) val graphState = new GraphState(graph) import graphState.{nodeState, children} - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List()) + println(children.lookupNow) + + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) } "GraphState add nodes" in { @@ -44,15 +64,35 @@ class GraphStateSpec extends FreeSpec with MustMatchers { assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) } "GraphState add edge" in { @@ -65,12 +105,26 @@ class GraphStateSpec extends FreeSpec with MustMatchers { import graphState.{nodeState, children} assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) graphState.update(GraphChanges(addEdges = Array(child("A", "C")))) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B", "A")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B", "A")) } "GraphState add nodes with edge" in { @@ -85,6 +139,8 @@ class GraphStateSpec extends FreeSpec with MustMatchers { graphState.update(GraphChanges(addNodes = Array("G","H"), addEdges = Array(child("G", "H")))) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("H")) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("H")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("H":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("G")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("H":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("G")) } "GraphState delete edge" in { @@ -99,6 +155,8 @@ class GraphStateSpec extends FreeSpec with MustMatchers { graphState.update(GraphChanges(delEdges = Array(child("E", "F")))) assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) } //TODO: delete for author LabeledProperty edges, where edges can have the same source/target combination From 8a937022309a86218457c3a1be717a80d91a92e8 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Fri, 12 Jul 2019 22:14:11 -0400 Subject: [PATCH 06/14] indirect lookup over edges --- README.md | 14 ++ build.sbt | 3 + .../webApp/state/graphstate/EdgeState.scala | 29 ++- .../webApp/state/graphstate/GraphState.scala | 13 +- .../webApp/state/graphstate/LayerState.scala | 79 ++++--- .../wust/webApp/state/EdgeStateSpec.scala | 57 ++++- .../wust/webApp/state/GraphStateSpec.scala | 197 +++++++++--------- 7 files changed, 240 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index b09983227..772263b3d 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,20 @@ networking.firewall.allowedTCPPorts = [ 12345 ]; The images are automatically published to docker.woost.space once a day from `master`. +# nodejs Stack traces in tests + +TODO: Why is this not working? + +```bash +npm install source-map-support +``` + +then run tests using + +``` +SOURCEMAPS=true start nsbt +``` + # Deployment Requirements: * docker diff --git a/build.sbt b/build.sbt index eae3b4e60..23d86d71b 100644 --- a/build.sbt +++ b/build.sbt @@ -160,6 +160,9 @@ lazy val webSettings = Seq( scalaJSStage in Test := FastOptStage, scalaJSLinkerConfig in (Compile, fastOptJS) ~= { _.withSourceMap(withSourceMaps) }, scalaJSLinkerConfig in (Compile, fullOptJS) ~= { _.withSourceMap(withSourceMaps) }, + scalaJSLinkerConfig in (Test, fastOptJS) ~= { _.withSourceMap(withSourceMaps) }, + scalaJSLinkerConfig in (Test, fullOptJS) ~= { _.withSourceMap(withSourceMaps) }, + npmDevDependencies in Compile ++= Deps.npm.webpackDependencies, version in webpack := Deps.webpackVersion, version in startWebpackDevServer := Deps.webpackDevServerVersion, diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala index eca44a2ef..65c874c71 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala @@ -14,23 +14,35 @@ import scala.collection.{ breakOut, immutable, mutable } object EdgeState { @inline def edgeKey(edge: Edge): (NodeId, NodeId) = edge.sourceId -> edge.targetId - def apply(edges: Array[Edge]):EdgeState = { - val edgeState = new EdgeState + def apply(nodeState:NodeState, edges: Array[Edge]): EdgeState = { + val edgeState = new EdgeState(nodeState) edgeState.update(GraphChanges(addEdges = edges)) edgeState } } -final class EdgeState { +final class EdgeState(nodeState: NodeState) { + val edgesNow: mutable.ArrayBuffer[Edge] = mutable.ArrayBuffer.empty val idToIdxHashMap: mutable.HashMap[(NodeId, NodeId), Int] = mutable.HashMap.empty val edgesRx: mutable.ArrayBuffer[Var[Edge]] = mutable.ArrayBuffer.empty + var edgesIdxNow: InterleavedArrayInt = InterleavedArrayInt.empty + @inline def idToIdxFold[T](endPoints: (NodeId, NodeId))(default: => T)(f: Int => T): T = { + idToIdxHashMap.get(endPoints) match { + case Some(idx) => f(idx) + case None => default + } + } + @inline def idToIdxForeach[U](endPoints: (NodeId, NodeId))(f: Int => U): Unit = idToIdxFold(endPoints)(())(f(_)) + @inline def idToIdxMap[T](endPoints: (NodeId, NodeId))(f: Int => T): Option[T] = idToIdxFold(endPoints)(Option.empty[T])(idx => Some(f(idx))) @inline def idToIdxOrThrow(endPoints: (NodeId, NodeId)): Int = idToIdxHashMap(endPoints) + def idToIdx(endPoints: (NodeId, NodeId)): Option[Int] = idToIdxFold[Option[Int]](endPoints)(None)(Some(_)) def update(changes: GraphChanges): Unit = { // register new and updated edges + val addEdgeIdxBuilder = new mutable.ArrayBuilder.ofInt changes.addEdges.foreachElement { edge => val key = EdgeState.edgeKey(edge) @@ -43,10 +55,19 @@ final class EdgeState { edgesNow += edge edgesRx += Var(edge) idToIdxHashMap(key) = newIdx + nodeState.idToIdxForeach(edge.sourceId){ sourceIdx => + nodeState.idToIdxForeach(edge.targetId){ targetIdx => + addEdgeIdxBuilder += sourceIdx + addEdgeIdxBuilder += targetIdx + } + } } assert(edgesNow.length == idToIdxHashMap.size) } + + edgesIdxNow = new InterleavedArrayInt(edgesIdxNow.interleaved ++ addEdgeIdxBuilder.result) + assert(edgesRx.size == edgesNow.size) + assert(edgesIdxNow.elementCount == edgesNow.size) } } - diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index dc69ddbbe..8ab577a6e 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -17,10 +17,9 @@ import scala.collection.{ breakOut, immutable, mutable } final class GraphState(initialGraph: Graph) { import initialGraph.{ nodes, edges } val nodeState = new NodeState - val edgeState = new EdgeState + val edgeState = new EdgeState(nodeState) // import nodeState.idToIdxForeach - // val n = initialGraph.size // private val consistentEdges = ArraySet.create(edges.length) // val edgesIdx = InterleavedArrayInt.create(edges.length) @@ -279,24 +278,22 @@ final class GraphState(initialGraph: Graph) { // val automatedEdgeReverseIdx: NestedArrayInt = automatedEdgeReverseIdxBuilder.result() // val derivedFromTemplateEdgeIdx: NestedArrayInt = derivedFromTemplateEdgeIdxBuilder.result() - - val children = new ChildrenLayer + val children = new ChildrenLayer(edgeState) // val read = new ChildrenLayer update(GraphChanges(addNodes = initialGraph.nodes, addEdges = initialGraph.edges)) def update(changes: GraphChanges) = { time("graphstate") { - edgeState.update(changes) val layerChanges = nodeState.update(changes) - children.update(nodeState, layerChanges) + edgeState.update(changes) + children.update(layerChanges) // read.update(nodeState, layerChanges) } } } - -final class ChildrenLayer extends LayerState { +final class ChildrenLayer(val edgeState: EdgeState) extends LayerState { @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { case edge: Edge.Child => code(edge.parentId, edge.childId) case _ => diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index 62ef62cbc..fae5466a2 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -18,45 +18,54 @@ final case class LayerChanges( ) abstract class LayerState { - var lookupNow: NestedArrayIntValues = NestedArrayInt.empty - var revLookupNow: NestedArrayIntValues = NestedArrayInt.empty - val lookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty - val revLookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty + // val nodeState: NodeState + val edgeState: EdgeState + import edgeState.edgesIdxNow + + var edgeLookupNow: NestedArrayIntValues = NestedArrayInt.empty + var edgeRevLookupNow: NestedArrayIntValues = NestedArrayInt.empty + val edgeLookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty + val edgeRevLookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty + + var lookupNow: NestedArrayIntMapped = edgeLookupNow.viewMapInt(edgesIdxNow.right) + var revLookupNow: NestedArrayIntMapped = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) + def lookupRx(idx: Int)(implicit ctx: Ctx.Owner) = edgeLookupRx(idx).map(_.map(edgesIdxNow.right)) + def revLookupRx(idx: Int)(implicit ctx: Ctx.Owner) = edgeRevLookupRx(idx).map(_.map(edgesIdxNow.left)) @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] - def update(nodeState: NodeState, changes: LayerChanges): Unit = { + def update(changes: LayerChanges): Unit = { val affectedSourceNodes = new mutable.ArrayBuffer[Int] val affectedTargetNodes = new mutable.ArrayBuffer[Int] - val addElemBuilder = new mutable.ArrayBuilder.ofRef[(Int,Int)] - val addRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int,Int)] - changes.addEdges.foreachElement { + val addElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] + val addRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] + changes.addEdges.foreachElement { ifMyEdge { (sourceId, targetId) => - nodeState.idToIdxForeach(sourceId) { sourceIdx => - nodeState.idToIdxForeach(targetId) { targetIdx => - addElemBuilder += sourceIdx -> targetIdx - addRevElemBuilder += targetIdx -> sourceIdx - affectedSourceNodes += sourceIdx - affectedTargetNodes += targetIdx - } + edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => + val sourceIdx = edgesIdxNow.left(edgeIdx) + val targetIdx = edgesIdxNow.right(edgeIdx) + addElemBuilder += sourceIdx -> edgeIdx + addRevElemBuilder += targetIdx -> edgeIdx + affectedSourceNodes += sourceIdx + affectedTargetNodes += targetIdx } } } val addElem = InterleavedArrayInt(addElemBuilder.result().sortBy(_._1)) //TODO: performance: build InterleavedArray directly to avoid tuples and reiteration in InterleavedArrayInt.apply, but how to sort? Put two ints into one long? val addRevElem = InterleavedArrayInt(addRevElemBuilder.result().sortBy(_._1)) - val delElemBuilder = new mutable.ArrayBuilder.ofRef[(Int,Int)] - val delRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int,Int)] + val delElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] + val delRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] changes.delEdges.foreach { ifMyEdge { (sourceId, targetId) => - nodeState.idToIdxForeach(sourceId) { sourceIdx => - nodeState.idToIdxForeach(targetId) { targetIdx => - delElemBuilder += sourceIdx -> lookupNow.indexOf(sourceIdx)(targetIdx) // Remove the first occurence of the sourceId/targetId combination - delRevElemBuilder += targetIdx -> lookupNow.indexOf(targetIdx)(sourceIdx) // Remove the first occurence of the sourceId/targetId combination - affectedSourceNodes += sourceIdx - affectedTargetNodes += targetIdx - } + edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => + val sourceIdx = edgesIdxNow.left(edgeIdx) + val targetIdx = edgesIdxNow.right(edgeIdx) + delElemBuilder += sourceIdx -> lookupNow.indexOf(sourceIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination + delRevElemBuilder += targetIdx -> lookupNow.indexOf(targetIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination + affectedSourceNodes += sourceIdx + affectedTargetNodes += targetIdx } } } @@ -68,24 +77,26 @@ abstract class LayerState { // addElem: InterleavedArrayInt // Array[idx -> elem] // delElem: InterleavedArrayInt // Array[idx -> position] if (scala.scalajs.LinkingInfo.developmentMode) { - lookupNow = lookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) - revLookupNow = revLookupNow.changedWithAssertions(changes.addIdx, addRevElem, delRevElem) - } - else { - lookupNow = lookupNow.changed(changes.addIdx, addElem, delElem) - revLookupNow = revLookupNow.changed(changes.addIdx, addRevElem, delRevElem) + edgeLookupNow = edgeLookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) + edgeRevLookupNow = edgeRevLookupNow.changedWithAssertions(changes.addIdx, addRevElem, delRevElem) + } else { + edgeLookupNow = edgeLookupNow.changed(changes.addIdx, addElem, delElem) + edgeRevLookupNow = edgeRevLookupNow.changed(changes.addIdx, addRevElem, delRevElem) } + lookupNow = edgeLookupNow.viewMapInt(edgesIdxNow.right) + revLookupNow = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) + loop(changes.addIdx) { _ => - lookupRx += Var(new Array[Int](0)) - revLookupRx += Var(new Array[Int](0)) + edgeLookupRx += Var(new Array[Int](0)) + edgeRevLookupRx += Var(new Array[Int](0)) } affectedSourceNodes.foreachElement { idx => - lookupRx(idx)() = lookupNow(idx).toArray + edgeLookupRx(idx)() = edgeLookupNow(idx).toArray } affectedTargetNodes.foreachElement { idx => - revLookupRx(idx)() = revLookupNow(idx).toArray + edgeRevLookupRx(idx)() = edgeRevLookupNow(idx).toArray } } } diff --git a/webApp/src/test/scala/wust/webApp/state/EdgeStateSpec.scala b/webApp/src/test/scala/wust/webApp/state/EdgeStateSpec.scala index 290e2a9ba..74961320d 100644 --- a/webApp/src/test/scala/wust/webApp/state/EdgeStateSpec.scala +++ b/webApp/src/test/scala/wust/webApp/state/EdgeStateSpec.scala @@ -13,14 +13,21 @@ class EdgeStateSpec extends FreeSpec with MustMatchers { def idToNode(id: String, content:String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText(content), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) "factory" in { + val nodes = Array[Node]("B", "C", "D", "E", "F") + val nodeState = NodeState(nodes) val edges = Array[Edge](child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) - val edgeState = EdgeState(edges) + val edgeState = EdgeState(nodeState, edges) assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))) == child("B", "C")) assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))) == child("B", "D")) assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))) == child("E", "F")) assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))) == child("F", "E")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("B", "C")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("B", "D")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("E", "F")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("F", "E")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))).now == child("B", "C")) assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))).now == child("B", "D")) assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))).now == child("E", "F")) @@ -28,28 +35,62 @@ class EdgeStateSpec extends FreeSpec with MustMatchers { } "add edges" in { + val nodes = Array[Node]("A", "B", "C", "D", "E", "F") + val nodeState = NodeState(nodes) val edges = Array[Edge](child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) - val edgeState = EdgeState(edges) + val edgeState = EdgeState(nodeState, edges) edgeState.update(GraphChanges(addEdges = Array(child("A", "C"), child("D", "E")))) + // old edges are still there + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))) == child("B", "C")) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))) == child("B", "D")) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))) == child("E", "F")) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))) == child("F", "E")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("B", "C")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("B", "D")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("E", "F")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("F", "E")) + + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))).now == child("B", "C")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))).now == child("B", "D")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))).now == child("E", "F")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))).now == child("F", "E")) + + // added edges assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))) == child("A", "C")) assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))) == child("D", "E")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("A", "C")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("D", "E")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))).now == child("A", "C")) assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))).now == child("D", "E")) } "update edge" in { + val nodes = Array[Node]("B", "C", "D", "E", "F") + val nodeState = NodeState(nodes) val edges = Array[Edge](child("B", "C"), child("B", "D"), child("E", "F"), child("F", "E")) - val edgeState = EdgeState(edges) + val edgeState = EdgeState(nodeState, edges) + + edgeState.update(GraphChanges(addEdges = Array(child("B", "C", Some(EpochMilli(0L))), child("F", "E", Some(EpochMilli(1L)))))) - edgeState.update(GraphChanges(addEdges = Array(child("A", "C", Some(EpochMilli(0L))), child("D", "E", Some(EpochMilli(1L)))))) + // old edges are still there + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))) == child("B", "D")) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))) == child("E", "F")) + + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("B", "D")) + assert(edgeState.edgesIdxNow.toIndexedSeq(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))).productIterator.asInstanceOf[Iterator[Int]].map(nodeState.nodesNow).toList == List[Node]("E", "F")) + + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("D":Cuid))).now == child("B", "D")) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("E":Cuid) -> NodeId("F":Cuid))).now == child("E", "F")) - assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))) == child("A", "C", Some(EpochMilli(0L)))) - assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))) == child("D", "E", Some(EpochMilli(1L)))) + // new and updated edges: + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))) == child("B", "C", Some(EpochMilli(0L)))) + assert(edgeState.edgesNow(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))) == child("F", "E", Some(EpochMilli(1L)))) - assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("A":Cuid) -> NodeId("C":Cuid))).now == child("A", "C", Some(EpochMilli(0L)))) - assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("D":Cuid) -> NodeId("E":Cuid))).now == child("D", "E", Some(EpochMilli(1L)))) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("B":Cuid) -> NodeId("C":Cuid))).now == child("B", "C", Some(EpochMilli(0L)))) + assert(edgeState.edgesRx(edgeState.idToIdxOrThrow(NodeId("F":Cuid) -> NodeId("E":Cuid))).now == child("F", "E", Some(EpochMilli(1L)))) } } diff --git a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala index a465aec77..c3946956f 100644 --- a/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala +++ b/webApp/src/test/scala/wust/webApp/state/GraphStateSpec.scala @@ -3,14 +3,17 @@ package wust.webApp.state.graphstate import org.scalatest._ import wust.graph._ import wust.ids._ +import rx._ class GraphStateSpec extends FreeSpec with MustMatchers { - def parent(childId:Cuid, parentId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) - def child(parentId:Cuid, childId:Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) - def user(id:Cuid) = Node.User(UserId(NodeId(id)), NodeData.User(id.toString, false, 0), NodeMeta.User) - implicit def stringToCuid(id:String):Cuid = Cuid.fromBase58String("5Q4is6Gc5NbA7T7W7PvAUw".dropRight(id.length) + id).right.get - implicit def idToNode(id: String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText("content"), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) - def idToNode(id: String, content:String): Node = Node.Content(id = NodeId(id:Cuid), data = NodeData.PlainText(content), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + def parent(childId: Cuid, parentId: Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) + def child(parentId: Cuid, childId: Cuid) = Edge.Child(ParentId(NodeId(parentId)), ChildId(NodeId(childId))) + def user(id: Cuid) = Node.User(UserId(NodeId(id)), NodeData.User(id.toString, false, 0), NodeMeta.User) + implicit def stringToCuid(id: String): Cuid = Cuid.fromBase58String("5Q4is6Gc5NbA7T7W7PvAUw".dropRight(id.length) + id).right.get + implicit def idToNode(id: String): Node = Node.Content(id = NodeId(id: Cuid), data = NodeData.PlainText("content"), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + def idToNode(id: String, content: String): Node = Node.Content(id = NodeId(id: Cuid), data = NodeData.PlainText(content), role = NodeRole.default, meta = NodeMeta(NodeAccess.ReadWrite)) + + implicit val ctx: Ctx.Owner = Ctx.Owner.safe() "GraphState factory" in { val graph = Graph( @@ -19,37 +22,35 @@ class GraphStateSpec extends FreeSpec with MustMatchers { ) val graphState = new GraphState(graph) - import graphState.{nodeState, children} - - println(children.lookupNow) - - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) - - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) - - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) - - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + import graphState.{ nodeState, edgeState, children } + + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("B": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("D": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("D": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("B": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("D": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("B": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("D": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) } "GraphState add nodes" in { @@ -60,39 +61,39 @@ class GraphStateSpec extends FreeSpec with MustMatchers { val graphState = new GraphState(graph) graphState.update(GraphChanges(addNodes = Array[Node]("G", "H"))) - import graphState.{nodeState, children} - - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("B":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("D":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + import graphState.{ nodeState, children } + + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("B": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("D": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("G": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("B": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C", "D")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("D": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("B": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("D": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("G": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("B": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("D": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("F")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("E")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("G": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) } "GraphState add edge" in { @@ -102,29 +103,29 @@ class GraphStateSpec extends FreeSpec with MustMatchers { ) val graphState = new GraphState(graph) - import graphState.{nodeState, children} + import graphState.{ nodeState, children } - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B")) graphState.update(GraphChanges(addEdges = Array(child("A", "C")))) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C")) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C")) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("C")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B", "A")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B", "A")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B", "A")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("A": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("C": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("B", "A")) } "GraphState add nodes with edge" in { @@ -134,13 +135,13 @@ class GraphStateSpec extends FreeSpec with MustMatchers { ) val graphState = new GraphState(graph) - import graphState.{nodeState, children} + import graphState.{ nodeState, children } - graphState.update(GraphChanges(addNodes = Array("G","H"), addEdges = Array(child("G", "H")))) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("H")) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("H")) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("H":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("G")) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("H":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("G")) + graphState.update(GraphChanges(addNodes = Array("G", "H"), addEdges = Array(child("G", "H")))) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("G": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("H")) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("G": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("H")) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("H": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("G")) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("H": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]("G")) } "GraphState delete edge" in { @@ -150,13 +151,13 @@ class GraphStateSpec extends FreeSpec with MustMatchers { ) val graphState = new GraphState(graph) - import graphState.{nodeState, children} + import graphState.{ nodeState, children } graphState.update(GraphChanges(delEdges = Array(child("E", "F")))) - assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) - assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F":Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupNow(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.lookupRx(nodeState.idToIdxOrThrow(NodeId("E": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupNow(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) + assert(children.revLookupRx(nodeState.idToIdxOrThrow(NodeId("F": Cuid))).now.map(idx => nodeState.nodesNow(idx).id).toList == List[Cuid]()) } //TODO: delete for author LabeledProperty edges, where edges can have the same source/target combination From c19beb6c7602a163d034636ec2e76580da132ba5 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Sat, 13 Jul 2019 16:12:14 -0400 Subject: [PATCH 07/14] Lazy rx updates, js native addElem sorting of interleaved ints in long --- .../webApp/state/graphstate/EdgeState.scala | 10 +- .../webApp/state/graphstate/GraphState.scala | 18 +- .../webApp/state/graphstate/LayerState.scala | 176 ++++++++++++------ .../webApp/state/graphstate/NodeState.scala | 7 + 4 files changed, 137 insertions(+), 74 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala index 65c874c71..cb44a60a5 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala @@ -1,5 +1,6 @@ package wust.webApp.state.graphstate +import acyclic.file import rx._ import flatland._ import wust.ids._ @@ -14,7 +15,7 @@ import scala.collection.{ breakOut, immutable, mutable } object EdgeState { @inline def edgeKey(edge: Edge): (NodeId, NodeId) = edge.sourceId -> edge.targetId - def apply(nodeState:NodeState, edges: Array[Edge]): EdgeState = { + def apply(nodeState: NodeState, edges: Array[Edge]): EdgeState = { val edgeState = new EdgeState(nodeState) edgeState.update(GraphChanges(addEdges = edges)) edgeState @@ -42,7 +43,7 @@ final class EdgeState(nodeState: NodeState) { def update(changes: GraphChanges): Unit = { // register new and updated edges - val addEdgeIdxBuilder = new mutable.ArrayBuilder.ofInt + val addEdgeIdxBuilder = InterleavedArrayInt.builder changes.addEdges.foreachElement { edge => val key = EdgeState.edgeKey(edge) @@ -57,8 +58,7 @@ final class EdgeState(nodeState: NodeState) { idToIdxHashMap(key) = newIdx nodeState.idToIdxForeach(edge.sourceId){ sourceIdx => nodeState.idToIdxForeach(edge.targetId){ targetIdx => - addEdgeIdxBuilder += sourceIdx - addEdgeIdxBuilder += targetIdx + addEdgeIdxBuilder.add(sourceIdx, targetIdx) } } } @@ -66,7 +66,7 @@ final class EdgeState(nodeState: NodeState) { assert(edgesNow.length == idToIdxHashMap.size) } - edgesIdxNow = new InterleavedArrayInt(edgesIdxNow.interleaved ++ addEdgeIdxBuilder.result) + edgesIdxNow = new InterleavedArrayInt(edgesIdxNow.interleaved ++ addEdgeIdxBuilder.result().interleaved) assert(edgesRx.size == edgesNow.size) assert(edgesIdxNow.elementCount == edgesNow.size) } diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index 8ab577a6e..0f351498e 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -1,5 +1,6 @@ package wust.webApp.state.graphstate +import acyclic.file import rx._ import flatland._ import wust.ids._ @@ -279,7 +280,7 @@ final class GraphState(initialGraph: Graph) { // val derivedFromTemplateEdgeIdx: NestedArrayInt = derivedFromTemplateEdgeIdxBuilder.result() val children = new ChildrenLayer(edgeState) - // val read = new ChildrenLayer + val read = new ReadLayer(edgeState) update(GraphChanges(addNodes = initialGraph.nodes, addEdges = initialGraph.edges)) @@ -287,8 +288,9 @@ final class GraphState(initialGraph: Graph) { time("graphstate") { val layerChanges = nodeState.update(changes) edgeState.update(changes) + children.update(layerChanges) - // read.update(nodeState, layerChanges) + read.update(layerChanges) } } } @@ -300,9 +302,9 @@ final class ChildrenLayer(val edgeState: EdgeState) extends LayerState { } } -// final class ReadLayer extends LayerState { -// @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { -// case edge: Edge.Read => code(edge.nodeId, edge.userId) -// case _ => -// } -// } +final class ReadLayer(val edgeState: EdgeState) extends LayerState { + @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { + case edge: Edge.Read => code(edge.nodeId, edge.userId) + case _ => + } +} diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index fae5466a2..73d8e5166 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -1,5 +1,7 @@ package wust.webApp.state.graphstate +import scala.scalajs.js.JSConverters._ +import acyclic.file import rx._ import flatland._ import wust.ids._ @@ -8,14 +10,46 @@ import wust.util.collection._ import wust.util.macros.InlineList import wust.graph._ import wust.util.time.time +import scala.scalajs.js import scala.collection.{ breakOut, immutable, mutable } +import scala.scalajs.js.WrappedArray -final case class LayerChanges( - addIdx: Int, - addEdges: Array[Edge] = Array.empty, - delEdges: Array[Edge] = Array.empty -) +@inline final class LazyReactiveWrapper(now: => NestedArrayIntValues) { + val self: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty + + @inline def grow(): Unit = { self += null } + + @inline def update(idx: Int, value: ArraySliceInt): Unit = { + if (self(idx) != null) { + self(idx)() = value.toArray + } + } + + @inline def apply(idx: Int): Var[Array[Int]] = { + if (self(idx) == null) { + val value = Var(now(idx).toArray) + self(idx) = value + value + } else { + self(idx) + } + } +} + +@inline final class InterleavedJSArrayIntBuilder { + @inline private def extractHi(l: Long): Int = (l >> 32).toInt + @inline private def extractLo(l: Long): Int = l.toInt + @inline private def combineToLong(a: Int, b: Int): Long = ((a.toLong) << 32) | (b & 0xffffffffL) + + val self = new js.Array[Long] + + @inline def add(a: Int, b: Int): Unit = { + self += combineToLong(a, b) + } + + @inline def result() = self +} abstract class LayerState { // val nodeState: NodeState @@ -25,8 +59,8 @@ abstract class LayerState { var edgeLookupNow: NestedArrayIntValues = NestedArrayInt.empty var edgeRevLookupNow: NestedArrayIntValues = NestedArrayInt.empty - val edgeLookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty - val edgeRevLookupRx: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty + val edgeLookupRx = new LazyReactiveWrapper(edgeLookupNow) + val edgeRevLookupRx = new LazyReactiveWrapper(edgeRevLookupNow) var lookupNow: NestedArrayIntMapped = edgeLookupNow.viewMapInt(edgesIdxNow.right) var revLookupNow: NestedArrayIntMapped = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) @@ -36,67 +70,87 @@ abstract class LayerState { @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] def update(changes: LayerChanges): Unit = { - val affectedSourceNodes = new mutable.ArrayBuffer[Int] - val affectedTargetNodes = new mutable.ArrayBuffer[Int] - val addElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] - val addRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] - changes.addEdges.foreachElement { - ifMyEdge { (sourceId, targetId) => - edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => - val sourceIdx = edgesIdxNow.left(edgeIdx) - val targetIdx = edgesIdxNow.right(edgeIdx) - addElemBuilder += sourceIdx -> edgeIdx - addRevElemBuilder += targetIdx -> edgeIdx - affectedSourceNodes += sourceIdx - affectedTargetNodes += targetIdx + time("graphstate:update") { + val affectedSourceNodes = new mutable.ArrayBuffer[Int] + val affectedTargetNodes = new mutable.ArrayBuffer[Int] + val addElemBuilder = new InterleavedJSArrayIntBuilder + val addRevElemBuilder = new InterleavedJSArrayIntBuilder + time("graphstate:update:addEdges") { + changes.addEdges.foreachElement { + ifMyEdge { (sourceId, targetId) => + edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => + val sourceIdx = edgesIdxNow.left(edgeIdx) + val targetIdx = edgesIdxNow.right(edgeIdx) + addElemBuilder.add(sourceIdx, edgeIdx) + addRevElemBuilder.add(targetIdx, edgeIdx) + affectedSourceNodes += sourceIdx + affectedTargetNodes += targetIdx + } + } } } - } - val addElem = InterleavedArrayInt(addElemBuilder.result().sortBy(_._1)) //TODO: performance: build InterleavedArray directly to avoid tuples and reiteration in InterleavedArrayInt.apply, but how to sort? Put two ints into one long? - val addRevElem = InterleavedArrayInt(addRevElemBuilder.result().sortBy(_._1)) - - val delElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] - val delRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] - changes.delEdges.foreach { - ifMyEdge { (sourceId, targetId) => - edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => - val sourceIdx = edgesIdxNow.left(edgeIdx) - val targetIdx = edgesIdxNow.right(edgeIdx) - delElemBuilder += sourceIdx -> lookupNow.indexOf(sourceIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination - delRevElemBuilder += targetIdx -> lookupNow.indexOf(targetIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination - affectedSourceNodes += sourceIdx - affectedTargetNodes += targetIdx + val addElem = time("graphstate:update:addElem") { + val interleavedLong = addElemBuilder.result() + // use native js Array.sort with compare function for long (faster than scala.util.Sorting.quickSort or java.util.Arrays.sort) + interleavedLong.sort(_ compare _) + new InterleavedArrayInt(interleavedLong.toArray) // TODO: is there a way to avoid copying? Either convert js.Array to scala array or call js.Array.sort on scala Array? + } + val addRevElem = time("graphstate:update:addRevElem") { + val interleavedLong = addRevElemBuilder.result() + // use native js Array.sort with compare function for long (faster than scala.util.Sorting.quickSort or java.util.Arrays.sort) + interleavedLong.sort(_ compare _) + new InterleavedArrayInt(interleavedLong.toArray) // TODO: is there a way to avoid copying? Either convert js.Array to scala array or call js.Array.sort on scala Array? + } + + val delElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] + val delRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] + time("graphstate:update:delEdges") { + changes.delEdges.foreach { + ifMyEdge { (sourceId, targetId) => + edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => + val sourceIdx = edgesIdxNow.left(edgeIdx) + val targetIdx = edgesIdxNow.right(edgeIdx) + delElemBuilder += sourceIdx -> lookupNow.indexOf(sourceIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination + delRevElemBuilder += targetIdx -> lookupNow.indexOf(targetIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination + affectedSourceNodes += sourceIdx + affectedTargetNodes += targetIdx + } + } } } - } - val delElem = InterleavedArrayInt(delElemBuilder.result().sortBy(_._1)) - val delRevElem = InterleavedArrayInt(delRevElemBuilder.result().sortBy(_._1)) - - // NestedArray.changed() parameters: - // addIdx: Int, // how many nodes are added - // addElem: InterleavedArrayInt // Array[idx -> elem] - // delElem: InterleavedArrayInt // Array[idx -> position] - if (scala.scalajs.LinkingInfo.developmentMode) { - edgeLookupNow = edgeLookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) - edgeRevLookupNow = edgeRevLookupNow.changedWithAssertions(changes.addIdx, addRevElem, delRevElem) - } else { - edgeLookupNow = edgeLookupNow.changed(changes.addIdx, addElem, delElem) - edgeRevLookupNow = edgeRevLookupNow.changed(changes.addIdx, addRevElem, delRevElem) - } + val delElem = time("graphstate:update:delElem") { InterleavedArrayInt(delElemBuilder.result().sortBy(_._1)) } + val delRevElem = time("graphstate:update:delRevElem") { InterleavedArrayInt(delRevElemBuilder.result().sortBy(_._1)) } - lookupNow = edgeLookupNow.viewMapInt(edgesIdxNow.right) - revLookupNow = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) + time("graphstate:update:change") { + // NestedArray.changed() parameters: + // addIdx: Int, // how many nodes are added + // addElem: InterleavedArrayInt // Array[idx -> elem] + // delElem: InterleavedArrayInt // Array[idx -> position] + if (scala.scalajs.LinkingInfo.developmentMode) { + edgeLookupNow = edgeLookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) + edgeRevLookupNow = edgeRevLookupNow.changedWithAssertions(changes.addIdx, addRevElem, delRevElem) + } else { + edgeLookupNow = edgeLookupNow.changed(changes.addIdx, addElem, delElem) + edgeRevLookupNow = edgeRevLookupNow.changed(changes.addIdx, addRevElem, delRevElem) + } + } - loop(changes.addIdx) { _ => - edgeLookupRx += Var(new Array[Int](0)) - edgeRevLookupRx += Var(new Array[Int](0)) - } + time("graphstate:update:update-rx") { + lookupNow = edgeLookupNow.viewMapInt(edgesIdxNow.right) + revLookupNow = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) - affectedSourceNodes.foreachElement { idx => - edgeLookupRx(idx)() = edgeLookupNow(idx).toArray - } - affectedTargetNodes.foreachElement { idx => - edgeRevLookupRx(idx)() = edgeRevLookupNow(idx).toArray + loop(changes.addIdx) { _ => + edgeLookupRx.grow() + edgeRevLookupRx.grow() + } + + affectedSourceNodes.foreachElement { idx => + edgeLookupRx(idx) = edgeLookupNow(idx) + } + affectedTargetNodes.foreachElement { idx => + edgeRevLookupRx(idx) = edgeRevLookupNow(idx) + } + } } } } diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala index 80fa6ac6a..e38b1233a 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala @@ -1,5 +1,6 @@ package wust.webApp.state.graphstate +import acyclic.file import rx._ import flatland._ import wust.ids._ @@ -63,3 +64,9 @@ final class NodeState { } } +final case class LayerChanges( + addIdx: Int, + addEdges: Array[Edge] = Array.empty, + delEdges: Array[Edge] = Array.empty +) + From 2db61e295473ab101560dfc64505b2946f5eb6dd Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Sat, 13 Jul 2019 17:35:14 -0400 Subject: [PATCH 08/14] wip --- .../webApp/state/graphstate/GraphState.scala | 22 +-- .../webApp/state/graphstate/LayerState.scala | 156 +++++++++--------- 2 files changed, 83 insertions(+), 95 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index 0f351498e..a18793cbd 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -279,8 +279,8 @@ final class GraphState(initialGraph: Graph) { // val automatedEdgeReverseIdx: NestedArrayInt = automatedEdgeReverseIdxBuilder.result() // val derivedFromTemplateEdgeIdx: NestedArrayInt = derivedFromTemplateEdgeIdxBuilder.result() - val children = new ChildrenLayer(edgeState) - val read = new ReadLayer(edgeState) + val children = new LayerState(edgeState) + val read = new LayerState(edgeState) update(GraphChanges(addNodes = initialGraph.nodes, addEdges = initialGraph.edges)) @@ -289,22 +289,8 @@ final class GraphState(initialGraph: Graph) { val layerChanges = nodeState.update(changes) edgeState.update(changes) - children.update(layerChanges) - read.update(layerChanges) + children.update(layerChanges, _.isInstanceOf[Edge.Child]) + read.update(layerChanges, _.isInstanceOf[Edge.Read]) } } } - -final class ChildrenLayer(val edgeState: EdgeState) extends LayerState { - @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { - case edge: Edge.Child => code(edge.parentId, edge.childId) - case _ => - } -} - -final class ReadLayer(val edgeState: EdgeState) extends LayerState { - @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] = { - case edge: Edge.Read => code(edge.nodeId, edge.userId) - case _ => - } -} diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index 73d8e5166..9253a77d0 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -1,5 +1,6 @@ package wust.webApp.state.graphstate +import scala.reflect.ClassTag import scala.scalajs.js.JSConverters._ import acyclic.file import rx._ @@ -37,7 +38,7 @@ import scala.scalajs.js.WrappedArray } } -@inline final class InterleavedJSArrayIntBuilder { +final class InterleavedJSArrayIntBuilder { @inline private def extractHi(l: Long): Int = (l >> 32).toInt @inline private def extractLo(l: Long): Int = l.toInt @inline private def combineToLong(a: Int, b: Int): Long = ((a.toLong) << 32) | (b & 0xffffffffL) @@ -51,10 +52,7 @@ import scala.scalajs.js.WrappedArray @inline def result() = self } -abstract class LayerState { - // val nodeState: NodeState - val edgeState: EdgeState - +final class LayerState(val edgeState: EdgeState) { import edgeState.edgesIdxNow var edgeLookupNow: NestedArrayIntValues = NestedArrayInt.empty @@ -67,90 +65,94 @@ abstract class LayerState { def lookupRx(idx: Int)(implicit ctx: Ctx.Owner) = edgeLookupRx(idx).map(_.map(edgesIdxNow.right)) def revLookupRx(idx: Int)(implicit ctx: Ctx.Owner) = edgeRevLookupRx(idx).map(_.map(edgesIdxNow.left)) - @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] - - def update(changes: LayerChanges): Unit = { - time("graphstate:update") { - val affectedSourceNodes = new mutable.ArrayBuffer[Int] - val affectedTargetNodes = new mutable.ArrayBuffer[Int] - val addElemBuilder = new InterleavedJSArrayIntBuilder - val addRevElemBuilder = new InterleavedJSArrayIntBuilder - time("graphstate:update:addEdges") { - changes.addEdges.foreachElement { - ifMyEdge { (sourceId, targetId) => - edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => - val sourceIdx = edgesIdxNow.left(edgeIdx) - val targetIdx = edgesIdxNow.right(edgeIdx) - addElemBuilder.add(sourceIdx, edgeIdx) - addRevElemBuilder.add(targetIdx, edgeIdx) - affectedSourceNodes += sourceIdx - affectedTargetNodes += targetIdx - } + @inline def update(changes: LayerChanges, accept: Edge => Boolean): Unit = { + // time("graphstate:update") { + val affectedSourceNodes = new mutable.ArrayBuilder.ofInt + val affectedTargetNodes = new mutable.ArrayBuilder.ofInt + val addElemBuilder = new InterleavedJSArrayIntBuilder + val addRevElemBuilder = new InterleavedJSArrayIntBuilder + time("graphstate:update:addEdges") { + changes.addEdges.foreachElement { edge => + if (accept(edge)) { + val sourceId = edge.sourceId + val targetId = edge.targetId + + edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => + val sourceIdx = edgesIdxNow.left(edgeIdx) + val targetIdx = edgesIdxNow.right(edgeIdx) + addElemBuilder.add(sourceIdx, edgeIdx) + addRevElemBuilder.add(targetIdx, edgeIdx) + affectedSourceNodes += sourceIdx + affectedTargetNodes += targetIdx } } } - val addElem = time("graphstate:update:addElem") { - val interleavedLong = addElemBuilder.result() - // use native js Array.sort with compare function for long (faster than scala.util.Sorting.quickSort or java.util.Arrays.sort) - interleavedLong.sort(_ compare _) - new InterleavedArrayInt(interleavedLong.toArray) // TODO: is there a way to avoid copying? Either convert js.Array to scala array or call js.Array.sort on scala Array? - } - val addRevElem = time("graphstate:update:addRevElem") { - val interleavedLong = addRevElemBuilder.result() - // use native js Array.sort with compare function for long (faster than scala.util.Sorting.quickSort or java.util.Arrays.sort) - interleavedLong.sort(_ compare _) - new InterleavedArrayInt(interleavedLong.toArray) // TODO: is there a way to avoid copying? Either convert js.Array to scala array or call js.Array.sort on scala Array? - } + } + val addElem = time("graphstate:update:addElem") { + val interleavedLong = addElemBuilder.result() + // use native js Array.sort with compare function for long (faster than scala.util.Sorting.quickSort or java.util.Arrays.sort) + interleavedLong.sort(_ compare _) + new InterleavedArrayInt(interleavedLong.toArray) // TODO: is there a way to avoid copying? Either convert js.Array to scala array or call js.Array.sort on scala Array? + } + val addRevElem = time("graphstate:update:addRevElem") { + val interleavedLong = addRevElemBuilder.result() + // use native js Array.sort with compare function for long (faster than scala.util.Sorting.quickSort or java.util.Arrays.sort) + interleavedLong.sort(_ compare _) + new InterleavedArrayInt(interleavedLong.toArray) // TODO: is there a way to avoid copying? Either convert js.Array to scala array or call js.Array.sort on scala Array? + } - val delElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] - val delRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] - time("graphstate:update:delEdges") { - changes.delEdges.foreach { - ifMyEdge { (sourceId, targetId) => - edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => - val sourceIdx = edgesIdxNow.left(edgeIdx) - val targetIdx = edgesIdxNow.right(edgeIdx) - delElemBuilder += sourceIdx -> lookupNow.indexOf(sourceIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination - delRevElemBuilder += targetIdx -> lookupNow.indexOf(targetIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination - affectedSourceNodes += sourceIdx - affectedTargetNodes += targetIdx - } + val delElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] + val delRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] + time("graphstate:update:delEdges") { + changes.delEdges.foreach { edge => + if (accept(edge)) { + val sourceId = edge.sourceId + val targetId = edge.targetId + + edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => + val sourceIdx = edgesIdxNow.left(edgeIdx) + val targetIdx = edgesIdxNow.right(edgeIdx) + delElemBuilder += sourceIdx -> lookupNow.indexOf(sourceIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination + delRevElemBuilder += targetIdx -> lookupNow.indexOf(targetIdx)(edgeIdx) // Remove the first occurence of the sourceId/targetId combination + affectedSourceNodes += sourceIdx + affectedTargetNodes += targetIdx } } } - val delElem = time("graphstate:update:delElem") { InterleavedArrayInt(delElemBuilder.result().sortBy(_._1)) } - val delRevElem = time("graphstate:update:delRevElem") { InterleavedArrayInt(delRevElemBuilder.result().sortBy(_._1)) } - - time("graphstate:update:change") { - // NestedArray.changed() parameters: - // addIdx: Int, // how many nodes are added - // addElem: InterleavedArrayInt // Array[idx -> elem] - // delElem: InterleavedArrayInt // Array[idx -> position] - if (scala.scalajs.LinkingInfo.developmentMode) { - edgeLookupNow = edgeLookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) - edgeRevLookupNow = edgeRevLookupNow.changedWithAssertions(changes.addIdx, addRevElem, delRevElem) - } else { - edgeLookupNow = edgeLookupNow.changed(changes.addIdx, addElem, delElem) - edgeRevLookupNow = edgeRevLookupNow.changed(changes.addIdx, addRevElem, delRevElem) - } + } + val delElem = time("graphstate:update:delElem") { InterleavedArrayInt(delElemBuilder.result().sortBy(_._1)) } + val delRevElem = time("graphstate:update:delRevElem") { InterleavedArrayInt(delRevElemBuilder.result().sortBy(_._1)) } + + time("graphstate:update:change") { + // NestedArray.changed() parameters: + // addIdx: Int, // how many nodes are added + // addElem: InterleavedArrayInt // Array[idx -> elem] + // delElem: InterleavedArrayInt // Array[idx -> position] + if (scala.scalajs.LinkingInfo.developmentMode) { + edgeLookupNow = edgeLookupNow.changedWithAssertions(changes.addIdx, addElem, delElem) + edgeRevLookupNow = edgeRevLookupNow.changedWithAssertions(changes.addIdx, addRevElem, delRevElem) + } else { + edgeLookupNow = edgeLookupNow.changed(changes.addIdx, addElem, delElem) + edgeRevLookupNow = edgeRevLookupNow.changed(changes.addIdx, addRevElem, delRevElem) } + } - time("graphstate:update:update-rx") { - lookupNow = edgeLookupNow.viewMapInt(edgesIdxNow.right) - revLookupNow = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) + time("graphstate:update:update-rx") { + lookupNow = edgeLookupNow.viewMapInt(edgesIdxNow.right) + revLookupNow = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) - loop(changes.addIdx) { _ => - edgeLookupRx.grow() - edgeRevLookupRx.grow() - } + loop(changes.addIdx) { _ => + edgeLookupRx.grow() + edgeRevLookupRx.grow() + } - affectedSourceNodes.foreachElement { idx => - edgeLookupRx(idx) = edgeLookupNow(idx) - } - affectedTargetNodes.foreachElement { idx => - edgeRevLookupRx(idx) = edgeRevLookupNow(idx) - } + affectedSourceNodes.result().foreachElement { idx => + edgeLookupRx(idx) = edgeLookupNow(idx) + } + affectedTargetNodes.result().foreachElement { idx => + edgeRevLookupRx(idx) = edgeRevLookupNow(idx) } } + // } } } From af34d2a4d409c3ccd0ab4c8c2994a7a15dafc700 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Sat, 13 Jul 2019 17:41:17 -0400 Subject: [PATCH 09/14] wip --- .../webApp/state/graphstate/GraphState.scala | 19 +++++++++++++++---- .../webApp/state/graphstate/LayerState.scala | 19 +++++++------------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index a18793cbd..775f6ac14 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -279,8 +279,8 @@ final class GraphState(initialGraph: Graph) { // val automatedEdgeReverseIdx: NestedArrayInt = automatedEdgeReverseIdxBuilder.result() // val derivedFromTemplateEdgeIdx: NestedArrayInt = derivedFromTemplateEdgeIdxBuilder.result() - val children = new LayerState(edgeState) - val read = new LayerState(edgeState) + val children = new LayerState(edgeState, edgeDistributors.ifMyEdgeChild) + val read = new LayerState(edgeState, edgeDistributors.ifMyEdgeRead) update(GraphChanges(addNodes = initialGraph.nodes, addEdges = initialGraph.edges)) @@ -289,8 +289,19 @@ final class GraphState(initialGraph: Graph) { val layerChanges = nodeState.update(changes) edgeState.update(changes) - children.update(layerChanges, _.isInstanceOf[Edge.Child]) - read.update(layerChanges, _.isInstanceOf[Edge.Read]) + children.update(layerChanges) + read.update(layerChanges) } } } + +object edgeDistributors { + def ifMyEdgeChild(code: (NodeId, NodeId) => Unit): Edge => Unit = { + case edge: Edge.Child => code(edge.parentId, edge.childId) + case _ => + } + def ifMyEdgeRead(code: (NodeId, NodeId) => Unit): Edge => Unit = { + case edge: Edge.Read => code(edge.nodeId, edge.userId) + case _ => + } +} diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index 9253a77d0..bbaa2ccab 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -52,7 +52,7 @@ final class InterleavedJSArrayIntBuilder { @inline def result() = self } -final class LayerState(val edgeState: EdgeState) { +final class LayerState(val edgeState: EdgeState, ifMyEdge: ((NodeId, NodeId) => Unit) => (Edge => Unit)) { import edgeState.edgesIdxNow var edgeLookupNow: NestedArrayIntValues = NestedArrayInt.empty @@ -65,18 +65,16 @@ final class LayerState(val edgeState: EdgeState) { def lookupRx(idx: Int)(implicit ctx: Ctx.Owner) = edgeLookupRx(idx).map(_.map(edgesIdxNow.right)) def revLookupRx(idx: Int)(implicit ctx: Ctx.Owner) = edgeRevLookupRx(idx).map(_.map(edgesIdxNow.left)) - @inline def update(changes: LayerChanges, accept: Edge => Boolean): Unit = { + // @inline def ifMyEdge(code: (NodeId, NodeId) => Unit): PartialFunction[Edge, Unit] + @inline def update(changes: LayerChanges): Unit = { // time("graphstate:update") { val affectedSourceNodes = new mutable.ArrayBuilder.ofInt val affectedTargetNodes = new mutable.ArrayBuilder.ofInt val addElemBuilder = new InterleavedJSArrayIntBuilder val addRevElemBuilder = new InterleavedJSArrayIntBuilder time("graphstate:update:addEdges") { - changes.addEdges.foreachElement { edge => - if (accept(edge)) { - val sourceId = edge.sourceId - val targetId = edge.targetId - + changes.addEdges.foreachElement { + ifMyEdge { (sourceId, targetId) => edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => val sourceIdx = edgesIdxNow.left(edgeIdx) val targetIdx = edgesIdxNow.right(edgeIdx) @@ -104,11 +102,8 @@ final class LayerState(val edgeState: EdgeState) { val delElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] val delRevElemBuilder = new mutable.ArrayBuilder.ofRef[(Int, Int)] time("graphstate:update:delEdges") { - changes.delEdges.foreach { edge => - if (accept(edge)) { - val sourceId = edge.sourceId - val targetId = edge.targetId - + changes.delEdges.foreach { + ifMyEdge { (sourceId, targetId) => edgeState.idToIdxForeach(sourceId -> targetId) { edgeIdx => val sourceIdx = edgesIdxNow.left(edgeIdx) val targetIdx = edgesIdxNow.right(edgeIdx) From 608bee8dbcf1cbd5b469f72ca63a3ff3eabc71aa Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Sun, 14 Jul 2019 15:28:59 -0400 Subject: [PATCH 10/14] Remove comments --- .../webApp/state/graphstate/GraphState.scala | 260 +----------------- 1 file changed, 1 insertion(+), 259 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index 775f6ac14..e8df66d63 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -14,270 +14,12 @@ import scala.collection.{ breakOut, immutable, mutable } //TODO: idempotence //TODO: commutativity (e.g. store edges without loaded nodes until the nodes appear) +//TODO: be immune to inconsistency final class GraphState(initialGraph: Graph) { import initialGraph.{ nodes, edges } val nodeState = new NodeState val edgeState = new EdgeState(nodeState) - // import nodeState.idToIdxForeach - - // val n = initialGraph.size - // private val consistentEdges = ArraySet.create(edges.length) - // val edgesIdx = InterleavedArrayInt.create(edges.length) - - // TODO: have one big triple nested array for all edge lookups? - - // To avoid array builders for each node, we collect the node degrees in a - // loop and then add the indices in a second loop. This is twice as fast - // than using one loop with arraybuilders. (A lot less allocations) - // private val outDegree = new Array[Int](n) - // private val parentsDegree = new Array[Int](n) - // private val contentsDegree = new Array[Int](n) - // private val readDegree = new Array[Int](n) - // private val childrenDegree = new Array[Int](n) - // private val messageChildrenDegree = new Array[Int](n) - // private val taskChildrenDegree = new Array[Int](n) - // private val noteChildrenDegree = new Array[Int](n) - // private val projectChildrenDegree = new Array[Int](n) - // private val tagChildrenDegree = new Array[Int](n) - // private val tagParentsDegree = new Array[Int](n) - // private val stageParentsDegree = new Array[Int](n) - // private val notDeletedParentsDegree = new Array[Int](n) - // private val notDeletedChildrenDegree = new Array[Int](n) - // private val authorshipDegree = new Array[Int](n) - // private val membershipsForNodeDegree = new Array[Int](n) - // private val notifyByUserDegree = new Array[Int](n) - // private val pinnedNodeDegree = new Array[Int](n) - // private val inviteNodeDegree = new Array[Int](n) - // private val expandedEdgesDegree = new Array[Int](n) - // private val assignedNodesDegree = new Array[Int](n) - // private val assignedUsersDegree = new Array[Int](n) - // private val propertiesDegree = new Array[Int](n) - // private val propertiesReverseDegree = new Array[Int](n) - // private val automatedDegree = new Array[Int](n) - // private val automatedReverseDegree = new Array[Int](n) - // private val derivedFromTemplateDegree = new Array[Int](n) - - // private val buildNow = EpochMilli.now - - // edges.foreachIndexAndElement { (edgeIdx, edge) => - // idToIdxForeach(edge.sourceId) { sourceIdx => - // idToIdxForeach(edge.targetId) { targetIdx => - // consistentEdges.add(edgeIdx) - // edgesIdx.updatea(edgeIdx, sourceIdx) - // edgesIdx.updateb(edgeIdx, targetIdx) - // // outDegree(sourceIdx) += 1 - // // edge match { - // // case e: Edge.Content => contentsDegree(sourceIdx) += 1 - // // case _ => - // // } - - // edge match { - // // case _: Edge.Author => - // // authorshipDegree(sourceIdx) += 1 - // // case _: Edge.Member => - // // membershipsForNodeDegree(sourceIdx) += 1 - // case e: Edge.Child => - // // val childIsMessage = nodes(targetIdx).role == NodeRole.Message - // // val childIsTask = nodes(targetIdx).role == NodeRole.Task - // // val childIsNote = nodes(targetIdx).role == NodeRole.Note - // // val childIsProject = nodes(targetIdx).role == NodeRole.Project - // // val childIsTag = nodes(targetIdx).role == NodeRole.Tag - // // val parentIsTag = nodes(sourceIdx).role == NodeRole.Tag - // // val parentIsStage = nodes(sourceIdx).role == NodeRole.Stage - // // parentsDegree(targetIdx) += 1 - // childrenDegree(sourceIdx) += 1 - - // // if (childIsProject) projectChildrenDegree(sourceIdx) += 1 - // // if (childIsMessage) messageChildrenDegree(sourceIdx) += 1 - // // if (childIsTask) taskChildrenDegree(sourceIdx) += 1 - // // if (childIsNote) noteChildrenDegree(sourceIdx) += 1 - - // // e.data.deletedAt match { - // // case None => - // // if (childIsTag) tagChildrenDegree(sourceIdx) += 1 - // // if (parentIsTag) tagParentsDegree(targetIdx) += 1 - // // if (parentIsStage) stageParentsDegree(targetIdx) += 1 - // // notDeletedParentsDegree(targetIdx) += 1 - // // notDeletedChildrenDegree(sourceIdx) += 1 - // // case Some(deletedAt) => - // // if (deletedAt isAfter buildNow) { // in the future - // // if (childIsTag) tagChildrenDegree(sourceIdx) += 1 - // // if (parentIsTag) tagParentsDegree(targetIdx) += 1 - // // if (parentIsStage) stageParentsDegree(targetIdx) += 1 - // // notDeletedParentsDegree(targetIdx) += 1 - // // notDeletedChildrenDegree(sourceIdx) += 1 - // // } - // // // TODO everything deleted further in the past should already be filtered in backend - // // // BUT received on request - // // } - // // case _: Edge.Assigned => - // // assignedNodesDegree(targetIdx) += 1 - // // assignedUsersDegree(sourceIdx) += 1 - // // case _: Edge.Expanded => - // // expandedEdgesDegree(sourceIdx) += 1 - // // case _: Edge.Notify => - // // notifyByUserDegree(targetIdx) += 1 - // // case _: Edge.Pinned => - // // pinnedNodeDegree(targetIdx) += 1 - // // case _: Edge.Invite => - // // inviteNodeDegree(targetIdx) += 1 - // // case _: Edge.LabeledProperty => - // // propertiesDegree(sourceIdx) += 1 - // // propertiesReverseDegree(targetIdx) += 1 - // // case _: Edge.Automated => - // // automatedDegree(sourceIdx) += 1 - // // automatedReverseDegree(targetIdx) += 1 - // // case _: Edge.DerivedFromTemplate => - // // derivedFromTemplateDegree(sourceIdx) += 1 - // case _: Edge.Read => - // readDegree(sourceIdx) += 1 - // case _ => - // } - // } - // } - // } - - // private val outgoingEdgeIdxBuilder = NestedArrayInt.builder(outDegree) - // private val parentsIdxBuilder = NestedArrayInt.builder(parentsDegree) - // private val parentEdgeIdxBuilder = NestedArrayInt.builder(parentsDegree) - // private val contentsEdgeIdxBuilder = NestedArrayInt.builder(contentsDegree) - // private val readEdgeIdxBuilder = NestedArrayInt.builder(readDegree) - // private val childrenIdxBuilder = NestedArrayInt.builder(childrenDegree) - // private val childEdgeIdxBuilder = NestedArrayInt.builder(childrenDegree) - // private val messageChildrenIdxBuilder = NestedArrayInt.builder(messageChildrenDegree) - // private val taskChildrenIdxBuilder = NestedArrayInt.builder(taskChildrenDegree) - // private val noteChildrenIdxBuilder = NestedArrayInt.builder(noteChildrenDegree) - // private val projectChildrenIdxBuilder = NestedArrayInt.builder(projectChildrenDegree) - // private val tagChildrenIdxBuilder = NestedArrayInt.builder(tagChildrenDegree) - // private val tagParentsIdxBuilder = NestedArrayInt.builder(tagParentsDegree) - // private val stageParentsIdxBuilder = NestedArrayInt.builder(stageParentsDegree) - // private val notDeletedParentsIdxBuilder = NestedArrayInt.builder(notDeletedParentsDegree) - // private val notDeletedChildrenIdxBuilder = NestedArrayInt.builder(notDeletedChildrenDegree) - // private val authorshipEdgeIdxBuilder = NestedArrayInt.builder(authorshipDegree) - // private val authorIdxBuilder = NestedArrayInt.builder(authorshipDegree) - // private val membershipEdgeForNodeIdxBuilder = NestedArrayInt.builder(membershipsForNodeDegree) - // private val notifyByUserIdxBuilder = NestedArrayInt.builder(notifyByUserDegree) - // private val pinnedNodeIdxBuilder = NestedArrayInt.builder(pinnedNodeDegree) - // private val inviteNodeIdxBuilder = NestedArrayInt.builder(inviteNodeDegree) - // private val expandedEdgeIdxBuilder = NestedArrayInt.builder(expandedEdgesDegree) - // private val assignedNodesIdxBuilder = NestedArrayInt.builder(assignedNodesDegree) - // private val assignedUsersIdxBuilder = NestedArrayInt.builder(assignedUsersDegree) - // private val propertiesEdgeIdxBuilder = NestedArrayInt.builder(propertiesDegree) - // private val propertiesEdgeReverseIdxBuilder = NestedArrayInt.builder(propertiesReverseDegree) - // private val automatedEdgeIdxBuilder = NestedArrayInt.builder(automatedDegree) - // private val automatedEdgeReverseIdxBuilder = NestedArrayInt.builder(automatedReverseDegree) - // private val derivedFromTemplateEdgeIdxBuilder = NestedArrayInt.builder(derivedFromTemplateDegree) - - // consistentEdges.foreach { edgeIdx => - // val sourceIdx = edgesIdx.a(edgeIdx) - // val targetIdx = edgesIdx.b(edgeIdx) - // val edge = edges(edgeIdx) - // // outgoingEdgeIdxBuilder.add(sourceIdx, edgeIdx) - - // // edge match { - // // case e: Edge.Content => contentsEdgeIdxBuilder.add(sourceIdx, edgeIdx) - - // // case _ => - // // } - - // edge match { - // // case _: Edge.Author => - // // authorshipEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // // authorIdxBuilder.add(sourceIdx, targetIdx) - // // case _: Edge.Member => - // // membershipEdgeForNodeIdxBuilder.add(sourceIdx, edgeIdx) - // case e: Edge.Child => - // // val childIsMessage = nodes(targetIdx).role == NodeRole.Message - // // val childIsTask = nodes(targetIdx).role == NodeRole.Task - // // val childIsNote = nodes(targetIdx).role == NodeRole.Note - // // val childIsTag = nodes(targetIdx).role == NodeRole.Tag - // // val childIsProject = nodes(targetIdx).role == NodeRole.Project - // // val parentIsTag = nodes(sourceIdx).role == NodeRole.Tag - // // val parentIsStage = nodes(sourceIdx).role == NodeRole.Stage - // // parentsIdxBuilder.add(targetIdx, sourceIdx) - // // parentEdgeIdxBuilder.add(targetIdx, edgeIdx) - // childrenIdxBuilder.add(sourceIdx, targetIdx) - // // childEdgeIdxBuilder.add(sourceIdx, edgeIdx) - - // // if (childIsProject) projectChildrenIdxBuilder.add(sourceIdx, targetIdx) - // // if (childIsMessage) messageChildrenIdxBuilder.add(sourceIdx, targetIdx) - // // if (childIsTask) taskChildrenIdxBuilder.add(sourceIdx, targetIdx) - // // if (childIsNote) noteChildrenIdxBuilder.add(sourceIdx, targetIdx) - - // // e.data.deletedAt match { - // // case None => - // // if (childIsTag) tagChildrenIdxBuilder.add(sourceIdx, targetIdx) - // // if (parentIsTag) tagParentsIdxBuilder.add(targetIdx, sourceIdx) - // // if (parentIsStage) stageParentsIdxBuilder.add(targetIdx, sourceIdx) - // // notDeletedParentsIdxBuilder.add(targetIdx, sourceIdx) - // // notDeletedChildrenIdxBuilder.add(sourceIdx, targetIdx) - // // case Some(deletedAt) => - // // if (deletedAt isAfter buildNow) { // in the future - // // if (childIsTag) tagChildrenIdxBuilder.add(sourceIdx, targetIdx) - // // if (parentIsTag) tagParentsIdxBuilder.add(targetIdx, sourceIdx) - // // if (parentIsStage) stageParentsIdxBuilder.add(targetIdx, sourceIdx) - // // notDeletedParentsIdxBuilder.add(targetIdx, sourceIdx) - // // notDeletedChildrenIdxBuilder.add(sourceIdx, targetIdx) - // // } - // // // TODO everything deleted further in the past should already be filtered in backend - // // // BUT received on request - // // } - // // case _: Edge.Expanded => - // // expandedEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // // case _: Edge.Assigned => - // // assignedNodesIdxBuilder.add(targetIdx, sourceIdx) - // // assignedUsersIdxBuilder.add(sourceIdx, targetIdx) - // // case _: Edge.Notify => - // // notifyByUserIdxBuilder.add(targetIdx, sourceIdx) - // // case _: Edge.Pinned => - // // pinnedNodeIdxBuilder.add(targetIdx, sourceIdx) - // // case _: Edge.Invite => - // // inviteNodeIdxBuilder.add(targetIdx, sourceIdx) - // // case _: Edge.LabeledProperty => - // // propertiesEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // // propertiesEdgeReverseIdxBuilder.add(targetIdx, edgeIdx) - // // case _: Edge.Automated => - // // automatedEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // // automatedEdgeReverseIdxBuilder.add(targetIdx, edgeIdx) - // // case _: Edge.DerivedFromTemplate => - // // derivedFromTemplateEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // case _: Edge.Read => - // readEdgeIdxBuilder.add(sourceIdx, edgeIdx) - // case _ => - // } - // } - - // val outgoingEdgeIdx: NestedArrayInt = outgoingEdgeIdxBuilder.result() - // val parentsIdx: NestedArrayInt = parentsIdxBuilder.result() - // val parentEdgeIdx: NestedArrayInt = parentEdgeIdxBuilder.result() - // val readEdgeIdx: NestedArrayIntValues = readEdgeIdxBuilder.result() - // val childrenIdx: NestedArrayIntValues = childrenIdxBuilder.result() - // val childEdgeIdx: NestedArrayInt = childEdgeIdxBuilder.result() - // val contentsEdgeIdx: NestedArrayInt = contentsEdgeIdxBuilder.result() - // val messageChildrenIdx: NestedArrayInt = messageChildrenIdxBuilder.result() - // val taskChildrenIdx: NestedArrayInt = taskChildrenIdxBuilder.result() - // val noteChildrenIdx: NestedArrayInt = noteChildrenIdxBuilder.result() - // val tagChildrenIdx: NestedArrayInt = tagChildrenIdxBuilder.result() - // val projectChildrenIdx: NestedArrayInt = projectChildrenIdxBuilder.result() - // val tagParentsIdx: NestedArrayInt = tagParentsIdxBuilder.result() - // val stageParentsIdx: NestedArrayInt = stageParentsIdxBuilder.result() - // val notDeletedParentsIdx: NestedArrayInt = notDeletedParentsIdxBuilder.result() - // val notDeletedChildrenIdx: NestedArrayInt = notDeletedChildrenIdxBuilder.result() - // val authorshipEdgeIdx: NestedArrayInt = authorshipEdgeIdxBuilder.result() - // val membershipEdgeForNodeIdx: NestedArrayInt = membershipEdgeForNodeIdxBuilder.result() - // val notifyByUserIdx: NestedArrayInt = notifyByUserIdxBuilder.result() - // val authorsIdx: NestedArrayInt = authorIdxBuilder.result() - // val pinnedNodeIdx: NestedArrayInt = pinnedNodeIdxBuilder.result() - // val inviteNodeIdx: NestedArrayInt = inviteNodeIdxBuilder.result() - // val expandedEdgeIdx: NestedArrayInt = expandedEdgeIdxBuilder.result() - // val assignedNodesIdx: NestedArrayInt = assignedNodesIdxBuilder.result() // user -> node - // val assignedUsersIdx: NestedArrayInt = assignedUsersIdxBuilder.result() // node -> user - // val propertiesEdgeIdx: NestedArrayInt = propertiesEdgeIdxBuilder.result() // node -> property edge - // val propertiesEdgeReverseIdx: NestedArrayInt = propertiesEdgeReverseIdxBuilder.result() // node -> property edge - // val automatedEdgeIdx: NestedArrayInt = automatedEdgeIdxBuilder.result() - // val automatedEdgeReverseIdx: NestedArrayInt = automatedEdgeReverseIdxBuilder.result() - // val derivedFromTemplateEdgeIdx: NestedArrayInt = derivedFromTemplateEdgeIdxBuilder.result() val children = new LayerState(edgeState, edgeDistributors.ifMyEdgeChild) val read = new LayerState(edgeState, edgeDistributors.ifMyEdgeRead) From 62f2a9bdf42a2deffd9741984177aa4dbf7ecb95 Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Sun, 14 Jul 2019 15:35:59 -0400 Subject: [PATCH 11/14] Avoid ArraySlice allocation --- .../scala/wust/webApp/state/graphstate/LayerState.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index bbaa2ccab..b1e49b7e6 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -21,9 +21,9 @@ import scala.scalajs.js.WrappedArray @inline def grow(): Unit = { self += null } - @inline def update(idx: Int, value: ArraySliceInt): Unit = { + @inline def updateFrom(idx: Int, lookup: NestedArrayInt): Unit = { if (self(idx) != null) { - self(idx)() = value.toArray + self(idx)() = lookup(idx).toArray } } @@ -142,10 +142,10 @@ final class LayerState(val edgeState: EdgeState, ifMyEdge: ((NodeId, NodeId) => } affectedSourceNodes.result().foreachElement { idx => - edgeLookupRx(idx) = edgeLookupNow(idx) + edgeLookupRx.updateFrom(idx, edgeLookupNow) } affectedTargetNodes.result().foreachElement { idx => - edgeRevLookupRx(idx) = edgeRevLookupNow(idx) + edgeRevLookupRx.updateFrom(idx, edgeRevLookupNow) } } // } From e2a8d0c7dd63b4cf1366391aabf9854c6cfa7b3f Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Mon, 15 Jul 2019 13:30:45 -0400 Subject: [PATCH 12/14] sizehints --- .../webApp/state/graphstate/EdgeState.scala | 33 ++++++++++--------- .../webApp/state/graphstate/GraphState.scala | 4 +-- .../webApp/state/graphstate/LayerState.scala | 12 ++++--- .../webApp/state/graphstate/NodeState.scala | 12 +++++-- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala index cb44a60a5..498ab0ed3 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala @@ -42,30 +42,33 @@ final class EdgeState(nodeState: NodeState) { def update(changes: GraphChanges): Unit = { // register new and updated edges + + edgesNow.sizeHint(edgesNow.length + changes.addEdges.length) + edgesRx.sizeHint(edgesRx.length + changes.addEdges.length) + idToIdxHashMap.sizeHint(idToIdxHashMap.size + changes.addEdges.length) val addEdgeIdxBuilder = InterleavedArrayInt.builder changes.addEdges.foreachElement { edge => val key = EdgeState.edgeKey(edge) - idToIdxHashMap.get(key) match { - case Some(idx) => - edgesNow(idx) = edge - edgesRx(idx)() = edge - case None => - val newIdx = edgesNow.length - edgesNow += edge - edgesRx += Var(edge) - idToIdxHashMap(key) = newIdx - nodeState.idToIdxForeach(edge.sourceId){ sourceIdx => - nodeState.idToIdxForeach(edge.targetId){ targetIdx => - addEdgeIdxBuilder.add(sourceIdx, targetIdx) - } + idToIdxFold(key){ + val newIdx = edgesNow.length + edgesNow += edge + edgesRx += Var(edge) + idToIdxHashMap(key) = newIdx + nodeState.idToIdxForeach(edge.sourceId){ sourceIdx => + nodeState.idToIdxForeach(edge.targetId){ targetIdx => + addEdgeIdxBuilder.add(sourceIdx, targetIdx) } + } + }{ idx => + edgesNow(idx) = edge + edgesRx(idx)() = edge //TODO: LazyReactiveWrapper } - - assert(edgesNow.length == idToIdxHashMap.size) } + assert(edgesNow.length == idToIdxHashMap.size) + edgesIdxNow = new InterleavedArrayInt(edgesIdxNow.interleaved ++ addEdgeIdxBuilder.result().interleaved) assert(edgesRx.size == edgesNow.size) assert(edgesIdxNow.elementCount == edgesNow.size) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala index e8df66d63..af5faec1b 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/GraphState.scala @@ -28,8 +28,8 @@ final class GraphState(initialGraph: Graph) { def update(changes: GraphChanges) = { time("graphstate") { - val layerChanges = nodeState.update(changes) - edgeState.update(changes) + val layerChanges = time("graphstate:nodestate") {nodeState.update(changes)} + time("graphstate:edgestate") {edgeState.update(changes)} children.update(layerChanges) read.update(layerChanges) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index b1e49b7e6..8ddef5f67 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -19,6 +19,8 @@ import scala.scalajs.js.WrappedArray @inline final class LazyReactiveWrapper(now: => NestedArrayIntValues) { val self: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty + @inline def willBeIncreasedByHint(n:Int) = self.sizeHint(self.length + n) + @inline def grow(): Unit = { self += null } @inline def updateFrom(idx: Int, lookup: NestedArrayInt): Unit = { @@ -136,16 +138,18 @@ final class LayerState(val edgeState: EdgeState, ifMyEdge: ((NodeId, NodeId) => lookupNow = edgeLookupNow.viewMapInt(edgesIdxNow.right) revLookupNow = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) + edgeLookupRx.willBeIncreasedByHint(changes.addIdx) + edgeRevLookupRx.willBeIncreasedByHint(changes.addIdx) loop(changes.addIdx) { _ => edgeLookupRx.grow() edgeRevLookupRx.grow() } - affectedSourceNodes.result().foreachElement { idx => - edgeLookupRx.updateFrom(idx, edgeLookupNow) + affectedSourceNodes.result().foreachElement { sourceNodeIdx => + edgeLookupRx.updateFrom(sourceNodeIdx, edgeLookupNow) } - affectedTargetNodes.result().foreachElement { idx => - edgeRevLookupRx.updateFrom(idx, edgeRevLookupNow) + affectedTargetNodes.result().foreachElement { targetNodeIdx => + edgeRevLookupRx.updateFrom(targetNodeIdx, edgeRevLookupNow) } } // } diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala index e38b1233a..f9774831c 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala @@ -21,9 +21,9 @@ object NodeState { } final class NodeState { - val nodesNow: mutable.ArrayBuffer[Node] = mutable.ArrayBuffer.empty + val nodesNow: mutable.ArrayBuffer[Node] = mutable.ArrayBuffer.empty // faster than js.Array val idToIdxHashMap: mutable.HashMap[NodeId, Int] = mutable.HashMap.empty - val nodesRx: mutable.ArrayBuffer[Var[Node]] = mutable.ArrayBuffer.empty + val nodesRx: mutable.ArrayBuffer[Var[Node]] = mutable.ArrayBuffer.empty // faster than js.Array @inline def idToIdxFold[T](id: NodeId)(default: => T)(f: Int => T): T = { idToIdxHashMap.get(id) match { @@ -42,6 +42,12 @@ final class NodeState { // register new and updated nodes var addIdx = 0 // counts the number of newly added nodes + + // sizehints didn't make it faster... + // nodesNow.sizeHint(nodesNow.length + changes.addNodes.length) + // nodesRx.sizeHint(nodesRx.length + changes.addNodes.length) + // idToIdxHashMap.sizeHint(idToIdxHashMap.size + changes.addNodes.length) + changes.addNodes.foreachElement { node => val nodeId = node.id idToIdxFold(nodeId){ @@ -54,7 +60,7 @@ final class NodeState { }{ idx => // already exists, update node nodesNow(idx) = node - nodesRx(idx)() = node + nodesRx(idx)() = node //TODO: LazyReactiveWrapper } } From 8d9a2fcf17fa44e9dddfed886ef1135878fcb69e Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Mon, 15 Jul 2019 13:47:08 -0400 Subject: [PATCH 13/14] extract LazyReactiveCollection --- .../webApp/state/graphstate/LayerState.scala | 28 +------------ .../graphstate/LazyReactiveCollection.scala | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+), 26 deletions(-) create mode 100644 webApp/src/main/scala/wust/webApp/state/graphstate/LazyReactiveCollection.scala diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index 8ddef5f67..df5ccab97 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -16,30 +16,6 @@ import scala.scalajs.js import scala.collection.{ breakOut, immutable, mutable } import scala.scalajs.js.WrappedArray -@inline final class LazyReactiveWrapper(now: => NestedArrayIntValues) { - val self: mutable.ArrayBuffer[Var[Array[Int]]] = mutable.ArrayBuffer.empty - - @inline def willBeIncreasedByHint(n:Int) = self.sizeHint(self.length + n) - - @inline def grow(): Unit = { self += null } - - @inline def updateFrom(idx: Int, lookup: NestedArrayInt): Unit = { - if (self(idx) != null) { - self(idx)() = lookup(idx).toArray - } - } - - @inline def apply(idx: Int): Var[Array[Int]] = { - if (self(idx) == null) { - val value = Var(now(idx).toArray) - self(idx) = value - value - } else { - self(idx) - } - } -} - final class InterleavedJSArrayIntBuilder { @inline private def extractHi(l: Long): Int = (l >> 32).toInt @inline private def extractLo(l: Long): Int = l.toInt @@ -59,8 +35,8 @@ final class LayerState(val edgeState: EdgeState, ifMyEdge: ((NodeId, NodeId) => var edgeLookupNow: NestedArrayIntValues = NestedArrayInt.empty var edgeRevLookupNow: NestedArrayIntValues = NestedArrayInt.empty - val edgeLookupRx = new LazyReactiveWrapper(edgeLookupNow) - val edgeRevLookupRx = new LazyReactiveWrapper(edgeRevLookupNow) + val edgeLookupRx = new LazyReactiveCollection[Array[Int]](getCurrent = idx => edgeLookupNow(idx).toArray) + val edgeRevLookupRx = new LazyReactiveCollection[Array[Int]](getCurrent = idx => edgeRevLookupNow(idx).toArray) var lookupNow: NestedArrayIntMapped = edgeLookupNow.viewMapInt(edgesIdxNow.right) var revLookupNow: NestedArrayIntMapped = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LazyReactiveCollection.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LazyReactiveCollection.scala new file mode 100644 index 000000000..941dee6b4 --- /dev/null +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LazyReactiveCollection.scala @@ -0,0 +1,42 @@ +package wust.webApp.state.graphstate + +import scala.reflect.ClassTag +import scala.scalajs.js.JSConverters._ +import acyclic.file +import rx._ +import flatland._ +import wust.ids._ +import wust.util.algorithm._ +import wust.util.collection._ +import wust.util.macros.InlineList +import wust.graph._ +import wust.util.time.time +import scala.scalajs.js + +import scala.collection.{ breakOut, immutable, mutable } +import scala.scalajs.js.WrappedArray + +@inline final class LazyReactiveCollection[T](getCurrent: Int => T) { + val self: mutable.ArrayBuffer[Var[T]] = mutable.ArrayBuffer.empty + + @inline def willBeIncreasedByHint(n:Int) = self.sizeHint(self.length + n) + + @inline def grow(): Unit = { self += null } + + @inline def updateFrom(idx: Int, lookup: NestedArrayInt): Unit = { + if (self(idx) != null) { + self(idx)() = getCurrent(idx) + } + } + + @inline def apply(idx: Int): Var[T] = { + if (self(idx) == null) { + val value = Var(getCurrent(idx)) + self(idx) = value + value + } else { + self(idx) + } + } +} + From f235e663ea4ab5af78cfce6ca55add34c892ebbd Mon Sep 17 00:00:00 2001 From: Felix Dietze Date: Mon, 15 Jul 2019 14:14:59 -0400 Subject: [PATCH 14/14] Use LazyReactiveCollection in EdgeState and NodeState --- .../wust/webApp/state/graphstate/EdgeState.scala | 12 +++++++----- .../wust/webApp/state/graphstate/LayerState.scala | 8 ++++---- .../state/graphstate/LazyReactiveCollection.scala | 6 ++++-- .../wust/webApp/state/graphstate/NodeState.scala | 13 ++++++------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala index 498ab0ed3..8d3859971 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/EdgeState.scala @@ -26,7 +26,7 @@ final class EdgeState(nodeState: NodeState) { val edgesNow: mutable.ArrayBuffer[Edge] = mutable.ArrayBuffer.empty val idToIdxHashMap: mutable.HashMap[(NodeId, NodeId), Int] = mutable.HashMap.empty - val edgesRx: mutable.ArrayBuffer[Var[Edge]] = mutable.ArrayBuffer.empty + val edgesRx = new LazyReactiveCollection[Edge](getCurrent = idx => edgesNow(idx)) var edgesIdxNow: InterleavedArrayInt = InterleavedArrayInt.empty @inline def idToIdxFold[T](endPoints: (NodeId, NodeId))(default: => T)(f: Int => T): T = { @@ -54,7 +54,7 @@ final class EdgeState(nodeState: NodeState) { idToIdxFold(key){ val newIdx = edgesNow.length edgesNow += edge - edgesRx += Var(edge) + edgesRx.grow() idToIdxHashMap(key) = newIdx nodeState.idToIdxForeach(edge.sourceId){ sourceIdx => nodeState.idToIdxForeach(edge.targetId){ targetIdx => @@ -63,14 +63,16 @@ final class EdgeState(nodeState: NodeState) { } }{ idx => edgesNow(idx) = edge - edgesRx(idx)() = edge //TODO: LazyReactiveWrapper + edgesRx(idx)() = edge } } assert(edgesNow.length == idToIdxHashMap.size) + time("graphstate:edgestate:updateInterleaved") { edgesIdxNow = new InterleavedArrayInt(edgesIdxNow.interleaved ++ addEdgeIdxBuilder.result().interleaved) - assert(edgesRx.size == edgesNow.size) - assert(edgesIdxNow.elementCount == edgesNow.size) + } + assert(edgesRx.length == edgesNow.length) + assert(edgesIdxNow.elementCount == edgesNow.length) } } diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala index df5ccab97..3187f4f0c 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LayerState.scala @@ -114,18 +114,18 @@ final class LayerState(val edgeState: EdgeState, ifMyEdge: ((NodeId, NodeId) => lookupNow = edgeLookupNow.viewMapInt(edgesIdxNow.right) revLookupNow = edgeRevLookupNow.viewMapInt(edgesIdxNow.left) - edgeLookupRx.willBeIncreasedByHint(changes.addIdx) - edgeRevLookupRx.willBeIncreasedByHint(changes.addIdx) + edgeLookupRx.sizeHint(edgeLookupRx.length + changes.addIdx) + edgeRevLookupRx.sizeHint(edgeRevLookupRx.length + changes.addIdx) loop(changes.addIdx) { _ => edgeLookupRx.grow() edgeRevLookupRx.grow() } affectedSourceNodes.result().foreachElement { sourceNodeIdx => - edgeLookupRx.updateFrom(sourceNodeIdx, edgeLookupNow) + edgeLookupRx.refresh(sourceNodeIdx) } affectedTargetNodes.result().foreachElement { targetNodeIdx => - edgeRevLookupRx.updateFrom(targetNodeIdx, edgeRevLookupNow) + edgeRevLookupRx.refresh(targetNodeIdx) } } // } diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/LazyReactiveCollection.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/LazyReactiveCollection.scala index 941dee6b4..3a7b496b3 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/LazyReactiveCollection.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/LazyReactiveCollection.scala @@ -19,11 +19,13 @@ import scala.scalajs.js.WrappedArray @inline final class LazyReactiveCollection[T](getCurrent: Int => T) { val self: mutable.ArrayBuffer[Var[T]] = mutable.ArrayBuffer.empty - @inline def willBeIncreasedByHint(n:Int) = self.sizeHint(self.length + n) + @inline def length = self.length + + @inline def sizeHint(n:Int) = self.sizeHint(n) @inline def grow(): Unit = { self += null } - @inline def updateFrom(idx: Int, lookup: NestedArrayInt): Unit = { + @inline def refresh(idx: Int): Unit = { if (self(idx) != null) { self(idx)() = getCurrent(idx) } diff --git a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala index f9774831c..7ff4feda5 100644 --- a/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala +++ b/webApp/src/main/scala/wust/webApp/state/graphstate/NodeState.scala @@ -23,7 +23,7 @@ object NodeState { final class NodeState { val nodesNow: mutable.ArrayBuffer[Node] = mutable.ArrayBuffer.empty // faster than js.Array val idToIdxHashMap: mutable.HashMap[NodeId, Int] = mutable.HashMap.empty - val nodesRx: mutable.ArrayBuffer[Var[Node]] = mutable.ArrayBuffer.empty // faster than js.Array + val nodesRx = new LazyReactiveCollection[Node](idx => nodesNow(idx)) @inline def idToIdxFold[T](id: NodeId)(default: => T)(f: Int => T): T = { idToIdxHashMap.get(id) match { @@ -43,10 +43,9 @@ final class NodeState { var addIdx = 0 // counts the number of newly added nodes - // sizehints didn't make it faster... - // nodesNow.sizeHint(nodesNow.length + changes.addNodes.length) - // nodesRx.sizeHint(nodesRx.length + changes.addNodes.length) - // idToIdxHashMap.sizeHint(idToIdxHashMap.size + changes.addNodes.length) + nodesNow.sizeHint(nodesNow.length + changes.addNodes.length) + idToIdxHashMap.sizeHint(idToIdxHashMap.size + changes.addNodes.length) + nodesRx.sizeHint(nodesRx.length + changes.addNodes.length) changes.addNodes.foreachElement { node => val nodeId = node.id @@ -54,13 +53,13 @@ final class NodeState { // add new node and update idToIdxHashMap val newIdx = nodesNow.length nodesNow += node - nodesRx += Var(node) + nodesRx.grow() idToIdxHashMap(nodeId) = newIdx addIdx += 1 }{ idx => // already exists, update node nodesNow(idx) = node - nodesRx(idx)() = node //TODO: LazyReactiveWrapper + nodesRx.refresh(idx) } }