Skip to content

Commit

Permalink
feat: cache FDM-to-CDF-mapping for individual styling purposes (#3602)
Browse files Browse the repository at this point in the history
* feat: cache FDM-to-CDF-mapping for individual styling purposes
  • Loading branch information
haakonflatval-cognite authored Aug 25, 2023
1 parent 8cead58 commit 7c1cd0a
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 220 deletions.
240 changes: 166 additions & 74 deletions react-components/src/components/NodeCacheProvider/FdmNodeCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@
* Copyright 2023 Cognite AS
*/

import { type Node3D, type CogniteClient } from '@cognite/sdk';
import { type EdgeItem, type FdmSDK } from '../../utilities/FdmSDK';
import { type Node3D, type CogniteClient, type CogniteExternalId } from '@cognite/sdk';
import { type DmsUniqueIdentifier, type EdgeItem, type FdmSDK } from '../../utilities/FdmSDK';
import { RevisionFdmNodeCache } from './RevisionFdmNodeCache';
import {
type FdmEdgeWithNode,
type Fdm3dNodeData,
type FdmCadEdge,
type RevisionKey,
type RevisionTreeIndex,
type FdmKey,
type FdmId,
type ModelRevisionKey,
type RevisionId,
type NodeId,
type ModelNodeIdKey,
type ModelId
type ModelRevisionToEdgeMap,
type ModelRevisionId,
type FdmKey
} from './types';
import {
createFdmKey,
createModelNodeIdKey,
createModelRevisionKey,
revisionKeyToIds
} from './idAndKeyTranslation';
import {
type InModel3dEdgeProperties,
SYSTEM_3D_EDGE_SOURCE,
Expand All @@ -28,36 +33,139 @@ import { partition } from 'lodash';

import assert from 'assert';
import { fetchNodesForNodeIds } from './requests';

export type ModelRevisionKey = `${number}-${number}`;
export type ModelRevisionToEdgeMap = Map<ModelRevisionKey, FdmEdgeWithNode[]>;
import { type ThreeDModelMappings } from '../../hooks/types';

export class FdmNodeCache {
private readonly _revisionNodeCaches = new Map<RevisionKey, RevisionFdmNodeCache>();
private readonly _revisionNodeCaches = new Map<ModelRevisionKey, RevisionFdmNodeCache>();

private readonly _cdfClient: CogniteClient;
private readonly _fdmClient: FdmSDK;

private readonly _completeRevisions = new Set<RevisionKey>();
private readonly _completeRevisions = new Set<ModelRevisionKey>();

public constructor(cdfClient: CogniteClient, fdmClient: FdmSDK) {
this._cdfClient = cdfClient;
this._fdmClient = fdmClient;
}

public async getMappingsForFdmIds(
externalIds: DmsUniqueIdentifier[],
modelRevisionIds: ModelRevisionId[]
): Promise<ThreeDModelMappings[]> {
const [cachedModelRevisionIds, nonCachedModelRevisionIds] = partition(
modelRevisionIds,
(id) => {
const revisionKey = createModelRevisionKey(id.modelId, id.revisionId);
return this._completeRevisions.has(revisionKey);
}
);

const nonCachedModelMappings = await this.getNonCachedModelMappings(
externalIds,
nonCachedModelRevisionIds
);

const cachedModelMappings = this.getCachedModelMappings(cachedModelRevisionIds, externalIds);

const combinedList = [...cachedModelMappings, ...nonCachedModelMappings];
return combinedList;
}

private getCachedModelMappings(
modelRevisionIds: ModelRevisionId[],
externalIds: DmsUniqueIdentifier[]
): ThreeDModelMappings[] {
const inputExternalIdSet = new Set<FdmKey>(
externalIds.map((id) => createFdmKey(id.space, id.externalId))
);

return modelRevisionIds.map((modelRevisionId) => {
return this.getCachedModelMappingForRevision(modelRevisionId, inputExternalIdSet);
});
}

private getCachedModelMappingForRevision(
modelRevisionId: ModelRevisionId,
relevantFdmKeySet: Set<FdmKey>
): ThreeDModelMappings {
const revisionCache = this.getOrCreateRevisionCache(
modelRevisionId.modelId,
modelRevisionId.revisionId
);

const relevantCachedEdgeData = intersectWithStartNodeIdSet(
revisionCache.getAllEdges(),
relevantFdmKeySet
);

const mappings = new Map(
relevantCachedEdgeData.map((data) => [data.edge.startNode.externalId, data.node])
);

return {
...modelRevisionId,
mappings
};
}

private async getNonCachedModelMappings(
uniqueIds: DmsUniqueIdentifier[],
modelRevisions: ModelRevisionId[]
): Promise<ThreeDModelMappings[]> {
if (modelRevisions.length === 0 || uniqueIds.length === 0) {
return [];
}

const fdmKeySet = new Set(uniqueIds.map((id) => createFdmKey(id.space, id.externalId)));

const revisionToEdgesMap = await this.getAndCacheRevisionToEdgesMap(modelRevisions);

const modelDataPromises = modelRevisions.map(async ({ modelId, revisionId }) => {
const revisionKey = createModelRevisionKey(modelId, revisionId);
const edges = revisionToEdgesMap.get(revisionKey);

return this.getRelevantExternalIdToNodeMapForRevision(
{ modelId, revisionId },
edges,
fdmKeySet
);
});

return await Promise.all(modelDataPromises);
}

private getRelevantExternalIdToNodeMapForRevision(
{ modelId, revisionId }: ModelRevisionId,
edges: FdmEdgeWithNode[] | undefined,
relevantFdmKeySet: Set<FdmKey>
): ThreeDModelMappings {
if (edges === undefined || edges.length === 0)
return { modelId, revisionId, mappings: new Map<CogniteExternalId, Node3D>() };

const relevantEdges = intersectWithStartNodeIdSet(edges, relevantFdmKeySet);

const externalIdToNodeMap = new Map<CogniteExternalId, Node3D>(
relevantEdges.map((edge) => [edge.edge.startNode.externalId, edge.node])
);

return {
modelId,
revisionId,
mappings: externalIdToNodeMap
};
}

public async getAllMappingExternalIds(
modelRevisionIds: Array<{ modelId: number; revisionId: number }>
modelRevisionIds: ModelRevisionId[]
): Promise<ModelRevisionToEdgeMap> {
const [cachedRevisionIds, nonCachedRevisionIds] = partition(modelRevisionIds, (ids) => {
const key = createRevisionKey(ids.modelId, ids.revisionId);
const key = createModelRevisionKey(ids.modelId, ids.revisionId);
return this._completeRevisions.has(key);
});

const cachedEdges = cachedRevisionIds.map((id) => this.getCachedEdgesForRevision(id));

const revisionToEdgesMap = await this.getRevisionToEdgesMap(nonCachedRevisionIds);

this.writeRevisionDataToCache(revisionToEdgesMap);
const revisionToEdgesMap = await this.getAndCacheRevisionToEdgesMap(nonCachedRevisionIds);

cachedEdges.forEach(([revisionKey, edges]) => {
revisionToEdgesMap.set(revisionKey, edges);
Expand All @@ -69,14 +177,15 @@ export class FdmNodeCache {
private getCachedEdgesForRevision(id: {
modelId: number;
revisionId: number;
}): [RevisionKey, FdmEdgeWithNode[]] {
}): [ModelRevisionKey, FdmEdgeWithNode[]] {
const revisionCache = this.getOrCreateRevisionCache(id.modelId, id.revisionId);
const revisionKey = createRevisionKey(id.modelId, id.revisionId);
const revisionKey = createModelRevisionKey(id.modelId, id.revisionId);
const cachedRevisionEdges = revisionCache.getAllEdges();

return [revisionKey, cachedRevisionEdges];
}

private writeRevisionDataToCache(modelMap: Map<RevisionKey, FdmEdgeWithNode[]>): void {
private writeRevisionDataToCache(modelMap: Map<ModelRevisionKey, FdmEdgeWithNode[]>): void {
for (const [revisionKey, data] of modelMap.entries()) {
const [modelId, revisionId] = revisionKeyToIds(revisionKey);
const revisionCache = this.getOrCreateRevisionCache(modelId, revisionId);
Expand All @@ -89,12 +198,20 @@ export class FdmNodeCache {
}
}

private async getRevisionToEdgesMap(
modelRevisionIds: Array<{ modelId: number; revisionId: number }>
): Promise<Map<RevisionKey, FdmEdgeWithNode[]>> {
private async getAndCacheRevisionToEdgesMap(
modelRevisionIds: ModelRevisionId[]
): Promise<Map<ModelRevisionKey, FdmEdgeWithNode[]>> {
const revisionIds = modelRevisionIds.map((modelRevisionId) => modelRevisionId.revisionId);
const edges = await this.getEdgesForRevisions(revisionIds, this._fdmClient);
return await groupToModelRevision(edges, modelRevisionIds, this._cdfClient);
const revisionToEdgesMap = await createRevisionToEdgesMap(
edges,
modelRevisionIds,
this._cdfClient
);

this.writeRevisionDataToCache(revisionToEdgesMap);

return revisionToEdgesMap;
}

public async getClosestParentExternalId(
Expand Down Expand Up @@ -127,7 +244,7 @@ export class FdmNodeCache {
}

private getOrCreateRevisionCache(modelId: number, revisionId: number): RevisionFdmNodeCache {
const revisionKey = createRevisionKey(modelId, revisionId);
const revisionKey = createModelRevisionKey(modelId, revisionId);

const revisionCache = this._revisionNodeCaches.get(revisionKey);

Expand All @@ -148,49 +265,11 @@ export class FdmNodeCache {
}
}

function createRevisionKey(modelId: number, revisionId: number): RevisionKey {
return `${modelId}-${revisionId}`;
}

function revisionKeyToIds(revisionKey: RevisionKey): [number, number] {
const components = revisionKey.split('-');
return [Number(components[0]), Number(components[1])];
}

export function createRevisionTreeIndex(
modelId: number,
revisionId: number,
treeIndex: number
): RevisionTreeIndex {
return `${modelId}-${revisionId}-${treeIndex}`;
}

export function createFdmKey(spaceId: string, externalId: string): FdmKey {
return `${spaceId}-${externalId}`;
}

export function fdmKeyToId(fdmKey: FdmKey): FdmId {
const parts = fdmKey.split('-');

return { space: parts[0], externalId: parts[1] };
}

export function insertIntoSetMap<T, U>(key: T, value: U, globalMap: Map<T, U[]>): void {
const prevVal = globalMap.get(key);

if (prevVal === undefined) {
globalMap.set(key, [value]);
return;
}

prevVal.push(value);
}

async function groupToModelRevision(
async function createRevisionToEdgesMap(
edges: FdmCadEdge[],
modelRevisionIds: Array<{ modelId: number; revisionId: number }>,
modelRevisionIds: ModelRevisionId[],
cdfClient: CogniteClient
): Promise<Map<RevisionKey, FdmEdgeWithNode[]>> {
): Promise<Map<ModelRevisionKey, FdmEdgeWithNode[]>> {
const revisionToNodeIdsMap = createRevisionToNodeIdMap(edges);
const modelNodeIdToNodeMap = await createModelNodeIdToNodeMap(
revisionToNodeIdsMap,
Expand All @@ -213,12 +292,15 @@ async function groupToModelRevision(
}

function createFdmEdgeWithNode(
modelRevisionId: { modelId: number; revisionId: number },
modelRevisionId: ModelRevisionId,
edge: FdmCadEdge,
modelNodeIdToNodeMap: Map<ModelNodeIdKey, Node3D>
): FdmEdgeWithNode {
const revisionNodeIdKey =
`${modelRevisionId.modelId}-${modelRevisionId.revisionId}-${edge.properties.revisionNodeId}` as const;
const revisionNodeIdKey = createModelNodeIdKey(
modelRevisionId.modelId,
modelRevisionId.revisionId,
edge.properties.revisionNodeId
);

const node = modelNodeIdToNodeMap.get(revisionNodeIdKey);
assert(node !== undefined);
Expand All @@ -229,9 +311,9 @@ function createFdmEdgeWithNode(
function insertEdgeIntoMapList(
value: FdmEdgeWithNode,
map: Map<ModelRevisionKey, FdmEdgeWithNode[]>,
modelRevisionId: { modelId: number; revisionId: number }
modelRevisionId: ModelRevisionId
): void {
const modelRevisionIdKey: ModelRevisionKey = createRevisionKey(
const modelRevisionIdKey: ModelRevisionKey = createModelRevisionKey(
modelRevisionId.modelId,
modelRevisionId.revisionId
);
Expand All @@ -247,7 +329,7 @@ function insertEdgeIntoMapList(

async function createModelNodeIdToNodeMap(
revisionToNodeIdsMap: Map<RevisionId, NodeId[]>,
modelRevisionIds: Array<{ modelId: ModelId; revisionId: RevisionId }>,
modelRevisionIds: ModelRevisionId[],
cdfClient: CogniteClient
): Promise<Map<ModelNodeIdKey, Node3D>> {
const revisionNodeIdToNode = new Map<ModelNodeIdKey, Node3D>();
Expand All @@ -257,8 +339,8 @@ async function createModelNodeIdToNodeMap(
assert(modelId !== undefined);

const nodes = await fetchNodesForNodeIds(modelId, revisionId, nodeIds, cdfClient);
nodeIds.forEach((e, ind) => {
const modelNodeIdKey = `${modelId}-${revisionId}-${e}` as const;
nodeIds.forEach((nodeId, ind) => {
const modelNodeIdKey = createModelNodeIdKey(modelId, revisionId, nodeId);
revisionNodeIdToNode.set(modelNodeIdKey, nodes[ind]);
});
});
Expand All @@ -281,3 +363,13 @@ function createRevisionToNodeIdMap(edges: FdmCadEdge[]): Map<RevisionId, NodeId[
return revisionNodeIdMap;
}, new Map<RevisionId, NodeId[]>());
}

function intersectWithStartNodeIdSet(
edges: FdmEdgeWithNode[],
relevantFdmKeySet: Set<FdmKey>
): FdmEdgeWithNode[] {
return edges.filter((edgeData) => {
const fdmKey = createFdmKey(edgeData.edge.startNode.space, edgeData.edge.startNode.externalId);
return relevantFdmKeySet.has(fdmKey);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
*/

import { type ReactElement, type ReactNode, createContext, useContext, useMemo } from 'react';
import { FdmNodeCache, type ModelRevisionToEdgeMap } from './FdmNodeCache';
import { FdmNodeCache } from './FdmNodeCache';
import { type UseQueryResult, useQuery } from '@tanstack/react-query';
import { useFdmSdk, useSDK } from '../RevealContainer/SDKProvider';
import { type Fdm3dNodeData } from './types';
import { type ModelRevisionToEdgeMap, type Fdm3dNodeData } from './types';

import assert from 'assert';
import { type DmsUniqueIdentifier } from '../../utilities/FdmSDK';
import { type TypedReveal3DModel } from '../Reveal3DResources/types';
import { type ThreeDModelMappings } from '../../hooks/types';
import { DEFAULT_QUERY_STALE_TIME } from '../../utilities/constants';

export type FdmNodeCacheContent = {
cache: FdmNodeCache;
Expand Down Expand Up @@ -68,6 +72,24 @@ export const useFdm3dNodeData = (
return result;
};

export const useFdmAssetMappings = (
fdmAssetExternalIds: DmsUniqueIdentifier[],
models: TypedReveal3DModel[]
): UseQueryResult<ThreeDModelMappings[]> => {
const nodeCacheContent = useContext(FdmNodeCacheContext);

return useQuery(
['reveal', 'react-components', 'fdm-asset-mappings', fdmAssetExternalIds],
async () => {
return await nodeCacheContent?.cache.getMappingsForFdmIds(fdmAssetExternalIds, models);
},
{
enabled: fdmAssetExternalIds.length > 0 && models.length > 0,
staleTime: DEFAULT_QUERY_STALE_TIME
}
);
};

export function NodeCacheProvider({ children }: { children?: ReactNode }): ReactElement {
const fdmClient = useFdmSdk();
const cdfClient = useSDK();
Expand Down
Loading

0 comments on commit 7c1cd0a

Please sign in to comment.