Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react-components): improve asset mapping cache mechanism and refactoring to stabilize rule threshold styling and switching #4652

Merged
merged 33 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7ea39ca
refactoring to use caching and use treeindex instead of numeric range…
danpriori Jun 14, 2024
246970f
use the asset mappings per model cache to generate the cache per asse…
danpriori Jun 20, 2024
61d5aa4
add cache for node 3d when loading reveal 3d resources to speed up th…
danpriori Jun 24, 2024
71a3dd7
some wip refactoring on cache functions
danpriori Jun 25, 2024
4b848b3
minor refactoring
danpriori Jun 25, 2024
8eb42fa
refactoring and adding cache for assets for no mappings to skip reque…
danpriori Jun 26, 2024
f46889c
cleanup
danpriori Jun 26, 2024
5232873
separate caches for asset and node ids
danpriori Jun 26, 2024
f7baf3b
initial test for triggering callback to switch on off rule base styli…
danpriori Jun 27, 2024
5466801
Merge remote-tracking branch 'origin/master' into danpriori/rule-base…
danpriori Jun 28, 2024
2c1857c
add loading spinner and some ui changes
danpriori Jun 28, 2024
67a8b9d
split into different useEffects hooks to let render the spinner sooner
danpriori Jun 28, 2024
06309dd
dont use spinner on the reset button
danpriori Jun 28, 2024
c48d6f6
cleanup
danpriori Jul 1, 2024
78673b9
lint and remove unused function
danpriori Jul 1, 2024
9a5413e
missed call to show up outputs panel
danpriori Jul 2, 2024
01d9e41
changes from cr
danpriori Jul 3, 2024
fa47fce
changes from cr - splitting out selector component into smaller pieces
danpriori Jul 4, 2024
b04a961
move extract asset id from mapped to the general hooks folder
danpriori Jul 4, 2024
6820e25
move _amountOfAssetIdsChunks to a private readonly parameter
danpriori Jul 4, 2024
511c495
use the correct map key for asset ids
danpriori Jul 4, 2024
06c1f4b
refactoring from cr and using the resource context provider to link s…
danpriori Jul 8, 2024
f0282f2
refactoring and splitting asset mapping caches into smaller classes
danpriori Jul 9, 2024
f4f9d23
lint
danpriori Jul 9, 2024
a123bf9
turn splitChunkInCacheNode3D to private
danpriori Jul 10, 2024
c46f3cb
more refactoring - cr
danpriori Jul 10, 2024
818296a
lint
danpriori Jul 10, 2024
306ca38
removing not used rule based callback
danpriori Jul 10, 2024
2e9ec23
populate the both caches
danpriori Jul 10, 2024
58a592e
move hooks into the hooks sub folder
danpriori Jul 10, 2024
fa90456
Merge remote-tracking branch 'origin/master' into danpriori/rule-base…
danpriori Jul 11, 2024
44c7891
update import for Reveal3DResourcesInfoContext
danpriori Jul 11, 2024
63bf233
minor refactoring to force spinner more stable - still not fully but …
danpriori Jul 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
danpriori marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
/*!
* Copyright 2023 Cognite AS
*/

import {
type CogniteClient,
type AssetMapping3D,
type Node3D,
type CogniteInternalId
} from '@cognite/sdk';
import {
type ModelNodeIdKey,
type AssetId,
type ModelId,
type RevisionId,
type ChunkInCacheTypes,
type ModelAssetIdKey
} from './types';
import { chunk, maxBy } from 'lodash';
import assert from 'assert';
import { isValidAssetMapping, modelRevisionNodesAssetsToKey, modelRevisionToKey } from './utils';
import { type ModelWithAssetMappings } from './AssetMappingAndNode3DCacheProvider';
import { AssetMappingPerAssetIdCache } from './AssetMappingPerAssetIdCache';
import { AssetMappingPerNodeIdCache } from './AssetMappingPerNodeIdCache';
import { Node3DPerNodeIdCache } from './Node3DPerNodeIdCache';
import { AssetMappingPerModelCache } from './AssetMappingPerModelCache';

export type NodeAssetMappingResult = { node?: Node3D; mappings: AssetMapping[] };

export type AssetMapping = Required<AssetMapping3D>;
export class AssetMappingAndNode3DCache {
private readonly _sdk: CogniteClient;

private readonly modelToAssetMappingsCache: AssetMappingPerModelCache;

private readonly assetIdsToAssetMappingCache: AssetMappingPerAssetIdCache;

private readonly nodeIdsToAssetMappingCache: AssetMappingPerNodeIdCache;

private readonly nodeIdsToNode3DCache: Node3DPerNodeIdCache;

private readonly _amountOfAssetIdsChunks = 1;

constructor(sdk: CogniteClient) {
this._sdk = sdk;
this.assetIdsToAssetMappingCache = new AssetMappingPerAssetIdCache();
this.nodeIdsToAssetMappingCache = new AssetMappingPerNodeIdCache();
this.modelToAssetMappingsCache = new AssetMappingPerModelCache(this._sdk);
this.nodeIdsToNode3DCache = new Node3DPerNodeIdCache(this._sdk);
}

public async getAssetMappingsForLowestAncestor(
modelId: ModelId,
revisionId: RevisionId,
ancestors: Node3D[]
): Promise<NodeAssetMappingResult> {
if (ancestors.length === 0) {
return { mappings: [] };
}

const searchTreeIndices = new Set(ancestors.map((ancestor) => ancestor.treeIndex));
const allNodeMappings = await this.getAssetMappingsForNodes(modelId, revisionId, ancestors);

const relevantMappings = allNodeMappings.filter((mapping) =>
searchTreeIndices.has(mapping.treeIndex)
);

if (relevantMappings.length === 0) {
return { mappings: [] };
}

const maxRelevantMappingTreeIndex = maxBy(
relevantMappings,
(mapping) => mapping.treeIndex
)?.treeIndex;

assert(maxRelevantMappingTreeIndex !== undefined);

const mappingsOfNearestAncestor = relevantMappings.filter(
(mapping) => mapping.treeIndex === maxRelevantMappingTreeIndex
);

const nearestMappedAncestor = ancestors.find(
(node) => node.treeIndex === maxRelevantMappingTreeIndex
);
assert(nearestMappedAncestor !== undefined);

return { node: nearestMappedAncestor, mappings: mappingsOfNearestAncestor };
}

public async getNodesForAssetIds(
modelId: ModelId,
revisionId: RevisionId,
assetIds: CogniteInternalId[]
): Promise<Map<AssetId, Node3D[]>> {
const relevantAssetIds = new Set(assetIds);

const assetIdsList = Array.from(relevantAssetIds);
const chunkSize = Math.round(assetIdsList.length / this._amountOfAssetIdsChunks);
const listChunks = chunk(assetIdsList, chunkSize);

const allAssetMappingsReturned = listChunks.map(async (itemChunk) => {
const assetMappings = await this.getAssetMappingsForAssetIds(modelId, revisionId, itemChunk);
return assetMappings;
});

const allAssetMappings = await Promise.all(allAssetMappingsReturned);
const assetMappings = allAssetMappings.flat();

const relevantAssetMappings = assetMappings.filter((mapping) =>
relevantAssetIds.has(mapping.assetId)
);

const nodes = await this.nodeIdsToNode3DCache.getNodesForNodeIds(
modelId,
revisionId,
relevantAssetMappings.map((assetMapping) => assetMapping.nodeId)
);

return nodes.reduce((acc, node, index) => {
const key = relevantAssetMappings[index].assetId;
const nodesForAsset = acc.get(key);

if (nodesForAsset !== undefined) {
nodesForAsset.push(node);
} else {
acc.set(key, [node]);
}

return acc;
}, new Map<AssetId, Node3D[]>());
}

public async generateNode3DCachePerItem(
modelId: ModelId,
revisionId: RevisionId,
nodeIds: number[] | undefined
): Promise<void> {
await this.nodeIdsToNode3DCache.generateNode3DCachePerItem(modelId, revisionId, nodeIds);
}

public async generateAssetMappingsCachePerItemFromModelCache(
modelId: ModelId,
revisionId: RevisionId,
assetMappingsPerModel: ModelWithAssetMappings[] | undefined
): Promise<void> {
if (assetMappingsPerModel === undefined) {
return;
}
assetMappingsPerModel.forEach(async (modelMapping) => {
modelMapping.assetMappings.forEach(async (item) => {
const key = modelRevisionNodesAssetsToKey(modelId, revisionId, [item.assetId]);
await this.assetIdsToAssetMappingCache.setAssetMappingsCacheItem(key, item);
});
});
}

public async getAssetMappingsForModel(
modelId: ModelId,
revisionId: RevisionId
): Promise<AssetMapping[]> {
const key = modelRevisionToKey(modelId, revisionId);
const cachedResult = await this.modelToAssetMappingsCache.getModelToAssetMappingCacheItems(key);

if (cachedResult !== undefined) {
return cachedResult;
}

return await this.modelToAssetMappingsCache.fetchAndCacheMappingsForModel(modelId, revisionId);
}

private async splitChunkInCacheAssetMappings(
currentChunk: number[],
modelId: ModelId,
revisionId: RevisionId,
type: string
): Promise<ChunkInCacheTypes<AssetMapping3D>> {
const chunkInCache: Array<Required<AssetMapping3D>> = [];
const chunkNotCached: number[] = [];

await Promise.all(
currentChunk.map(async (id) => {
const key = modelRevisionNodesAssetsToKey(modelId, revisionId, [id]);
const cachedResult = await this.getItemCacheResult(type, key);
if (cachedResult !== undefined) {
chunkInCache.push(...cachedResult);
} else {
chunkNotCached.push(id);
}
})
);

return { chunkInCache, chunkNotInCache: chunkNotCached };
}

private async getItemCacheResult(
type: string,
key: ModelNodeIdKey | ModelAssetIdKey
): Promise<AssetMapping[] | undefined> {
return type === 'nodeIds'
? await this.nodeIdsToAssetMappingCache.getNodeIdsToAssetMappingCacheItem(key)
: await this.assetIdsToAssetMappingCache.getAssetIdsToAssetMappingCacheItem(key);
}

private setItemCacheResult(
type: string,
key: ModelNodeIdKey | ModelAssetIdKey,
item: AssetMapping[] | undefined
): void {
const value = Promise.resolve(item ?? []);
type === 'nodeIds'
? this.nodeIdsToAssetMappingCache.setNodeIdsToAssetMappingCacheItem(key, value)
: this.assetIdsToAssetMappingCache.setAssetIdsToAssetMappingCacheItem(key, value);
}

private async fetchAssetMappingsRequest(
currentChunk: number[],
filterType: string,
modelId: ModelId,
revisionId: RevisionId
): Promise<AssetMapping[]> {
let assetMapping3D: AssetMapping3D[] = [];

if (currentChunk.length === 0) {
return [];
}
const filter =
filterType === 'nodeIds' ? { nodeIds: currentChunk } : { assetIds: currentChunk };

assetMapping3D = await this._sdk.assetMappings3D
.filter(modelId, revisionId, {
limit: 1000,
filter
})
.autoPagingToArray({ limit: Infinity });

assetMapping3D.forEach(async (item) => {
const key: ModelNodeIdKey = modelRevisionNodesAssetsToKey(modelId, revisionId, [
item.assetId
]);
if (filterType === 'nodeIds') {
await this.nodeIdsToAssetMappingCache.setAssetMappingsCacheItem(key, item);
} else {
await this.assetIdsToAssetMappingCache.setAssetMappingsCacheItem(key, item);
}
});
danpriori marked this conversation as resolved.
Show resolved Hide resolved

currentChunk.forEach(async (id) => {
const key = modelRevisionNodesAssetsToKey(modelId, revisionId, [id]);
const cachedResult = await this.getItemCacheResult(filterType, key);

if (cachedResult === undefined) {
this.setItemCacheResult(filterType, key, []);
}
});

return assetMapping3D.filter(isValidAssetMapping);
}

private async fetchMappingsInQueue(
index: number,
idChunks: number[][],
filterType: string,
modelId: ModelId,
revisionId: RevisionId,
assetMappingsList: Array<Required<AssetMapping3D>>
): Promise<AssetMapping3D[]> {
const assetMappings = await this.fetchAssetMappingsRequest(
idChunks[index],
filterType,
modelId,
revisionId
);

assetMappingsList = assetMappingsList.concat(assetMappings);
if (index >= idChunks.length - 1) {
return assetMappingsList;
}

const nextIndex = index + 1;
return await this.fetchMappingsInQueue(
nextIndex,
idChunks,
filterType,
modelId,
revisionId,
assetMappingsList
);
}

private async fetchAndCacheMappingsForIds(
modelId: ModelId,
revisionId: RevisionId,
ids: number[],
filterType: string
): Promise<AssetMapping[]> {
if (ids.length === 0) {
return [];
}
const idChunks = chunk(ids, 100);
const initialIndex = 0;
const assetMappings = await this.fetchMappingsInQueue(
initialIndex,
idChunks,
filterType,
modelId,
revisionId,
[]
);
return assetMappings;
}

private async getAssetMappingsForNodes(
modelId: ModelId,
revisionId: RevisionId,
nodes: Node3D[]
): Promise<AssetMapping[]> {
const nodeIds = nodes.map((node) => node.id);

const { chunkNotInCache, chunkInCache } = await this.splitChunkInCacheAssetMappings(
nodeIds,
modelId,
revisionId,
'nodeIds'
);

const notCachedNodeIds: number[] = chunkNotInCache;

const assetMappings = await this.fetchAndCacheMappingsForIds(
modelId,
revisionId,
notCachedNodeIds,
'nodeIds'
);

const allAssetMappings = chunkInCache.concat(assetMappings);
return allAssetMappings;
}

private async getAssetMappingsForAssetIds(
modelId: ModelId,
revisionId: RevisionId,
assetIds: number[]
): Promise<AssetMapping[]> {
const { chunkNotInCache, chunkInCache } = await this.splitChunkInCacheAssetMappings(
assetIds,
modelId,
revisionId,
'assetIds'
);

const notCachedAssetIds: number[] = chunkNotInCache;

const assetMappings = await this.fetchAndCacheMappingsForIds(
modelId,
revisionId,
notCachedAssetIds,
'assetIds'
);
const allAssetMappings = chunkInCache.concat(assetMappings);
return allAssetMappings;
}
}
Loading
Loading