diff --git a/pkg/interface/src/logic/api/graph.ts b/pkg/interface/src/logic/api/graph.ts index 1bb7e1054..57a4591e3 100644 --- a/pkg/interface/src/logic/api/graph.ts +++ b/pkg/interface/src/logic/api/graph.ts @@ -347,12 +347,18 @@ export default class GraphApi extends BaseApi { this.store.handleEvent({ data }); } - async getDeepNodesUpTo(ship: string, resource: string, startTime = null, count: number) { + async getDeepNewest(ship: string, resource: string, startTime = null, count: number) { const start = startTime ? decToUd(startTime) : null; const data = await this.scry('graph-store', `/deep-nodes-up-to/${ship}/${resource}/${count}/${start}` ); - this.store.handleEvent({ data }); + const node = data['graph-update']; + this.store.handleEvent({ + data: { + 'graph-update-flat': node, + 'graph-update': node + }, + }); } getGraphSubset(ship: string, resource: string, start: string, end: string) { diff --git a/pkg/interface/src/logic/reducers/graph-update.ts b/pkg/interface/src/logic/reducers/graph-update.ts index b04911913..a32ae6a5c 100644 --- a/pkg/interface/src/logic/reducers/graph-update.ts +++ b/pkg/interface/src/logic/reducers/graph-update.ts @@ -1,5 +1,6 @@ import { GraphNode } from '@urbit/api'; import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap'; +import BigIntArrayOrderedMap from '@urbit/api/lib/BigIntArrayOrderedMap'; import bigInt, { BigInteger } from 'big-integer'; import produce from 'immer'; import _ from 'lodash'; @@ -22,6 +23,12 @@ export const GraphReducer = (json) => { if(loose) { reduceState(useGraphState, loose, [addNodesLoose]); } + + const flat = _.get(json, 'graph-update-flat', false); + if (flat) { + reduceState(useGraphState, loose, [addNodesFlat]); + } + }; const addNodesLoose = (json: any, state: GraphState): GraphState => { @@ -39,6 +46,45 @@ const addNodesLoose = (json: any, state: GraphState): GraphState => { return state; }; +const addNodesFlat = (json: any, state: GraphState): GraphState => { + const data = _.get(json, 'add-nodes', false); + if (data) { + if (!('flatGraphs' in state)) { + return state; + } + + const resource = data.resource.ship + '/' + data.resource.name; + if (!(resource in state.flatGraphs)) { + state.flatGraphs[resource] = new BigIntArrayOrderedMap(); + } + const indices = Array.from(Object.keys(data.nodes)); + + indices.forEach((index) => { + const node = data.nodes[index]; + if (index.split('/').length === 0) { + return; + } + + const indexArr = index.split('/').slice(1).map((ind) => { + return bigInt(ind); + }); + + if (indexArr.length === 0) { + return state; + } + + state.flatGraphs[resource] = + state.flatGraphs[resource].set( + indexArr, + produce(node, (draft) => { + draft.children = mapifyChildren({}); + }) + ); + }); + } + return state; +}; + const keys = (json, state: GraphState): GraphState => { const data = _.get(json, 'keys', false); if (data) { diff --git a/pkg/interface/src/logic/state/graph.ts b/pkg/interface/src/logic/state/graph.ts index 569efc216..5f1f45627 100644 --- a/pkg/interface/src/logic/state/graph.ts +++ b/pkg/interface/src/logic/state/graph.ts @@ -2,6 +2,7 @@ import { Association, deSig, GraphNode, Graphs, resourceFromPath } from '@urbit/ import { useCallback } from 'react'; import { BaseState, createState } from './base'; + export interface GraphState extends BaseState { graphs: Graphs; graphKeys: Set; @@ -10,6 +11,7 @@ export interface GraphState extends BaseState { [index: string]: GraphNode; } }; + flatGraphs: FlatGraphs; pendingIndices: Record; graphTimesentMap: Record; // getKeys: () => Promise; diff --git a/pkg/npm/api/graph/types.ts b/pkg/npm/api/graph/types.ts index d5bc11bd4..8d8f1846c 100644 --- a/pkg/npm/api/graph/types.ts +++ b/pkg/npm/api/graph/types.ts @@ -1,5 +1,7 @@ import { Patp } from ".."; import BigIntOrderedMap from "../lib/BigIntOrderedMap"; +import BigIntArrayOrderedMap from "../lib/BigIntArrayOrderedMap"; + export interface TextContent { text: string; @@ -64,6 +66,15 @@ export interface GraphNode { post: Post; } +export interface FlatGraphNode { + children: null; + post: Post; +} + export type Graph = BigIntOrderedMap; export type Graphs = { [rid: string]: Graph }; + +export type FlatGraph = BigIntArrayOrderedMap; + +export type FlatGraphs = { [rid: string]: FlatGraph }; diff --git a/pkg/npm/api/lib/BigIntArrayOrderedMap.ts b/pkg/npm/api/lib/BigIntArrayOrderedMap.ts new file mode 100644 index 000000000..856996a4a --- /dev/null +++ b/pkg/npm/api/lib/BigIntArrayOrderedMap.ts @@ -0,0 +1,148 @@ +import produce, { immerable, castImmutable, castDraft, setAutoFreeze, enablePatches } from 'immer'; +import bigInt, { BigInteger } from "big-integer"; + +setAutoFreeze(false); + +enablePatches(); + +function sortBigInt(a: BigInteger[], b: BigInteger[]) { + if (a.lt(b)) { + return 1; + } else if (a.eq(b)) { + return 0; + } else { + return -1; + } +} + +function stringToBigIntArr(str: string) { + return str.split('/').slice(1).map((ind) => { + return bigInt(ind); + }); +} + +function arrToString(arr: BigInteger[]) { + let string = ''; + arr.forEach((key) => { + string = string + `/${key.toString()}`; + }); +} + +function sortBigIntArr(a: BigInteger[], b: BigInteger[]) { + let aLen = a.length; + let bLen = b.length; + + let i = 0; + while (i < aLen && i < bLen) { + if (a[i].lt(b[i])) { + return 1; + } else if (a[i].gt(b[i])) { + return -1; + } else { + i++; + } + } + + return aLen - bLen; +} + + +export default class BigIntArrayOrderedMap implements Iterable<[BigInteger[], V]> { + root: Record = {} + cachedIter: [BigInteger[], V][] = null; + [immerable] = true; + + constructor(items: [BigInteger[], V][] = []) { + items.forEach(([key, val]) => { + this.set(key, val); + }); + } + + get size() { + return Object.keys(this.root).length; + } + + + get(key: BigInteger[]) { + return this.root[arrToString(key)] ?? null; + } + + gas(items: [BigInteger[], V][]) { + return produce(this, draft => { + items.forEach(([key, value]) => { + draft.root[arrToString(key)] = castDraft(value); + }); + draft.generateCachedIter(); + }, + (patches) => { + //console.log(`gassed with ${JSON.stringify(patches, null, 2)}`); + }); + } + + set(key: BigInteger[], value: V) { + return produce(this, draft => { + draft.root[key.toString()] = castDraft(value); + draft.cachedIter = null; + }); + } + + clear() { + return produce(this, draft => { + draft.cachedIter = []; + draft.root = {} + }); + } + + has(key: BigInteger[]) { + return arrToString(key) in this.root; + } + + delete(key: BigInteger[]) { + const result = produce(this, draft => { + delete draft.root[arrToString(key)]; + draft.cachedIter = null; + }); + return result; + } + + [Symbol.iterator](): IterableIterator<[BigInteger[], V]> { + let idx = 0; + let result = this.generateCachedIter(); + return { + [Symbol.iterator]: this[Symbol.iterator], + next: (): IteratorResult<[BigInteger[], V]> => { + if (idx < result.length) { + return { value: result[idx++], done: false }; + } + return { done: true, value: null }; + }, + }; + } + + peekLargest() { + const sorted = Array.from(this); + return sorted[0] as [BigInteger[], V] | null; + } + + peekSmallest() { + const sorted = Array.from(this); + return sorted[sorted.length - 1] as [BigInteger[], V] | null; + } + + keys() { + return Array.from(this).map(([k,v]) => k); + } + + generateCachedIter() { + if(this.cachedIter) { + return [...this.cachedIter]; + } + const result = Object.keys(this.root).map(key => { + const num = stringtoBigIntArr(key); + return [num, this.root[key]] as [BigInteger[], V]; + }).sort(([a], [b]) => sortBigIntArr(a,b)); + this.cachedIter = result; + return [...result]; + } +} +