From bea4c64d85a147fad0c6bb53883beb99ffc19cbd Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Fri, 17 Apr 2020 11:42:59 -0700 Subject: [PATCH] use asset-manager for custom-properties - obtain function needs to return a value and assets - assets are stored per descriptor in model/structure - assets shold be released via customProperties.dispose() --- src/examples/basic-wrapper/coloring.ts | 2 +- .../proteopedia-wrapper/annotation.ts | 11 ++--- src/extensions/cellpack/model.ts | 2 +- src/extensions/cellpack/property.ts | 4 +- .../pdbe/structure-quality-report/prop.ts | 16 ++++---- .../rcsb/assembly-symmetry/behavior.ts | 8 ++-- src/extensions/rcsb/assembly-symmetry/prop.ts | 18 +++++---- .../rcsb/validation-report/behavior.ts | 6 +-- src/extensions/rcsb/validation-report/prop.ts | 25 ++++++------ .../common/custom-element-property.ts | 3 +- .../common/custom-model-property.ts | 7 +++- src/mol-model-props/common/custom-property.ts | 7 ++-- .../common/custom-structure-property.ts | 7 +++- .../computed/accessible-surface-area.ts | 2 +- src/mol-model-props/computed/interactions.ts | 2 +- .../computed/secondary-structure.ts | 4 +- src/mol-model-props/computed/valence-model.ts | 2 +- .../cross-link-restraint/property.ts | 2 +- .../structure/common/custom-property.ts | 28 ++++++++++--- src/mol-plugin-state/builder/structure.ts | 2 +- .../helpers/structure-selection-query.ts | 2 +- src/mol-plugin-state/transforms/model.ts | 37 ++++++++++++++++- .../transforms/representation.ts | 6 +-- src/mol-util/graphql-client.ts | 40 +++++++------------ src/tests/browser/render-structure.ts | 4 +- 25 files changed, 151 insertions(+), 96 deletions(-) diff --git a/src/examples/basic-wrapper/coloring.ts b/src/examples/basic-wrapper/coloring.ts index 0b5fe352b..024783c2f 100644 --- a/src/examples/basic-wrapper/coloring.ts +++ b/src/examples/basic-wrapper/coloring.ts @@ -18,7 +18,7 @@ export const StripedResidues = CustomElementProperty.create<number>({ for (let i = 0, _i = model.atomicHierarchy.atoms._rowCount; i < _i; i++) { map.set(i as ElementIndex, residueIndex[i] % 2); } - return map; + return { value: map }; }, coloring: { getColor(e) { return e === 0 ? Color(0xff0000) : Color(0x0000ff); }, diff --git a/src/examples/proteopedia-wrapper/annotation.ts b/src/examples/proteopedia-wrapper/annotation.ts index 647d0f603..1d158075e 100644 --- a/src/examples/proteopedia-wrapper/annotation.ts +++ b/src/examples/proteopedia-wrapper/annotation.ts @@ -9,6 +9,7 @@ import { CustomElementProperty } from '../../mol-model-props/common/custom-eleme import { Model, ElementIndex, ResidueIndex } from '../../mol-model/structure'; import { Color } from '../../mol-util/color'; import { CustomProperty } from '../../mol-model-props/common/custom-property'; +import { Asset } from '../../mol-util/assets'; const EvolutionaryConservationPalette: Color[] = [ [255, 255, 129], // insufficient @@ -30,9 +31,9 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({ type: 'static', async getData(model: Model, ctx: CustomProperty.Context) { const id = model.entryId.toLowerCase(); - const url = `https://proteopedia.org/cgi-bin/cnsrf?${id}`; - const json = await ctx.fetch({ url, type: 'json' }).runInContext(ctx.runtime); - const annotations = (json && json.residueAnnotations) || []; + const url = Asset.getUrlAsset(ctx.assetManager, `https://proteopedia.org/cgi-bin/cnsrf?${id}`); + const json = await ctx.assetManager.resolve(url, 'json').runInContext(ctx.runtime); + const annotations = json.data?.residueAnnotations || []; const conservationMap = new Map<string, number>(); @@ -58,7 +59,7 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({ } } - return map; + return { value: map, assets: [json] }; }, coloring: { getColor(e: number) { @@ -68,7 +69,7 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({ defaultColor: EvolutionaryConservationDefaultColor }, getLabel(e) { - if (e === 10) return `Evolutionary Conservation: InsufficientData`; + if (e === 10) return `Evolutionary Conservation: Insufficient Data`; return e ? `Evolutionary Conservation: ${e}` : void 0; } }); \ No newline at end of file diff --git a/src/extensions/cellpack/model.ts b/src/extensions/cellpack/model.ts index 06e0a9718..072850175 100644 --- a/src/extensions/cellpack/model.ts +++ b/src/extensions/cellpack/model.ts @@ -463,7 +463,7 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat const structure = packing.obj?.data; if (structure) { - await CellPackInfoProvider.attach({ fetch: plugin.fetch, runtime }, structure, { + await CellPackInfoProvider.attach({ runtime, assetManager: plugin.managers.asset }, structure, { info: { packingsCount: packings.length, packingIndex: i } }); } diff --git a/src/extensions/cellpack/property.ts b/src/extensions/cellpack/property.ts index 71c2e622f..700776fea 100644 --- a/src/extensions/cellpack/property.ts +++ b/src/extensions/cellpack/property.ts @@ -27,6 +27,8 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP getParams: (data: Structure) => CellPackInfoParams, isApplicable: (data: Structure) => true, obtain: async (ctx: CustomProperty.Context, data: Structure, props: CellPackInfoParams) => { - return { ...CellPackInfoParams.info.defaultValue, ...props.info }; + return { + value: { ...CellPackInfoParams.info.defaultValue, ...props.info } + }; } }); \ No newline at end of file diff --git a/src/extensions/pdbe/structure-quality-report/prop.ts b/src/extensions/pdbe/structure-quality-report/prop.ts index 8121efa07..fa020b940 100644 --- a/src/extensions/pdbe/structure-quality-report/prop.ts +++ b/src/extensions/pdbe/structure-quality-report/prop.ts @@ -21,6 +21,7 @@ import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif'; import { PropertyWrapper } from '../../../mol-model-props/common/wrapper'; import { CustomProperty } from '../../../mol-model-props/common/custom-property'; import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property'; +import { Asset } from '../../../mol-util/assets'; export { StructureQualityReport }; @@ -67,12 +68,12 @@ namespace StructureQualityReport { return { info, data: issueMap }; } - export async function fromServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<StructureQualityReport> { - const url = getEntryUrl(model.entryId, props.serverUrl); - const json = await ctx.fetch({ url, type: 'json' }).runInContext(ctx.runtime); - const data = json[model.entryId.toLowerCase()]; + export async function fromServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<CustomProperty.Data<StructureQualityReport>> { + const url = Asset.getUrlAsset(ctx.assetManager, getEntryUrl(model.entryId, props.serverUrl)); + const json = await ctx.assetManager.resolve(url, 'json').runInContext(ctx.runtime); + const data = json.data[model.entryId.toLowerCase()]; if (!data) throw new Error('missing data'); - return fromJson(model, data); + return { value: fromJson(model, data), assets: [json] }; } export function fromCif(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): StructureQualityReport | undefined { @@ -83,8 +84,9 @@ namespace StructureQualityReport { return { info, data: issueMap }; } - export async function fromCifOrServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<StructureQualityReport> { - return fromCif(ctx, model, props) || fromServer(ctx, model, props); + export async function fromCifOrServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<CustomProperty.Data<StructureQualityReport>> { + const cif = fromCif(ctx, model, props); + return cif ? { value: cif } : fromServer(ctx, model, props); } const _emptyArray: string[] = []; diff --git a/src/extensions/rcsb/assembly-symmetry/behavior.ts b/src/extensions/rcsb/assembly-symmetry/behavior.ts index 01c9f16c6..bebc5e868 100644 --- a/src/extensions/rcsb/assembly-symmetry/behavior.ts +++ b/src/extensions/rcsb/assembly-symmetry/behavior.ts @@ -80,7 +80,7 @@ export const InitAssemblySymmetry3D = StateAction.build({ isApplicable: (a) => AssemblySymmetry.isApplicable(a.data) })(({ a, ref, state }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => { try { - const propCtx = { runtime: ctx, fetch: plugin.fetch }; + const propCtx = { runtime: ctx, assetManager: plugin.managers.asset }; await AssemblySymmetryDataProvider.attach(propCtx, a.data); const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value; const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1; @@ -116,7 +116,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({ }, apply({ a, params }, plugin: PluginContext) { return Task.create('Assembly Symmetry', async ctx => { - await AssemblySymmetryProvider.attach({ runtime: ctx, fetch: plugin.fetch }, a.data); + await AssemblySymmetryProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data); const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value; if (!assemblySymmetry || assemblySymmetry.symbol === 'C1') { return StateObject.Null; @@ -129,7 +129,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({ }, update({ a, b, newParams }, plugin: PluginContext) { return Task.create('Assembly Symmetry', async ctx => { - await AssemblySymmetryProvider.attach({ runtime: ctx, fetch: plugin.fetch }, a.data); + await AssemblySymmetryProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data); const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value; if (!assemblySymmetry || assemblySymmetry.symbol === 'C1') { // this should NOT be StateTransformer.UpdateResult.Null @@ -172,7 +172,7 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({ if (!AssemblySymmetryDataProvider.get(structure).value) { await plugin.runTask(Task.create('Assembly Symmetry', async runtime => { - const propCtx = { runtime, fetch: plugin.fetch }; + const propCtx = { runtime, assetManager: plugin.managers.asset }; await AssemblySymmetryDataProvider.attach(propCtx, structure); const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value; const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1; diff --git a/src/extensions/rcsb/assembly-symmetry/prop.ts b/src/extensions/rcsb/assembly-symmetry/prop.ts index e4128ab3d..7bd128644 100644 --- a/src/extensions/rcsb/assembly-symmetry/prop.ts +++ b/src/extensions/rcsb/assembly-symmetry/prop.ts @@ -57,21 +57,23 @@ export namespace AssemblySymmetry { ); } - export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<AssemblySymmetryDataValue> { - if (!isApplicable(structure)) return []; + export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> { + if (!isApplicable(structure)) return { value: [] }; - const client = new GraphQLClient(props.serverUrl, ctx.fetch); + const client = new GraphQLClient(props.serverUrl, ctx.assetManager); const variables: AssemblySymmetryQueryVariables = { assembly_id: structure.units[0].conformation.operator.assembly?.id || 'deposited', entry_id: structure.units[0].model.entryId }; - const result = await client.request<AssemblySymmetryQuery>(ctx.runtime, query, variables); + const result = await client.request(ctx.runtime, query, variables); + let value: AssemblySymmetryDataValue = []; - if (!result.assembly?.rcsb_struct_symmetry) { + if (!result.data.assembly?.rcsb_struct_symmetry) { console.error('expected `rcsb_struct_symmetry` field'); - return []; + } else { + value = result.data.assembly.rcsb_struct_symmetry as AssemblySymmetryDataValue; } - return result.assembly.rcsb_struct_symmetry as AssemblySymmetryDataValue; + return { value, assets: [result] }; } /** Returns the index of the first non C1 symmetry or -1 */ @@ -194,6 +196,6 @@ export const AssemblySymmetryProvider: CustomStructureProperty.Provider<Assembly const assemblySymmetryData = AssemblySymmetryDataProvider.get(data).value; const assemblySymmetry = assemblySymmetryData?.[p.symmetryIndex]; if (!assemblySymmetry) new Error(`No assembly symmetry found for index ${p.symmetryIndex}`); - return assemblySymmetry; + return { value: assemblySymmetry }; } }); \ No newline at end of file diff --git a/src/extensions/rcsb/validation-report/behavior.ts b/src/extensions/rcsb/validation-report/behavior.ts index fed94bf7b..00b9d38fc 100644 --- a/src/extensions/rcsb/validation-report/behavior.ts +++ b/src/extensions/rcsb/validation-report/behavior.ts @@ -314,7 +314,7 @@ export const ValidationReportGeometryQualityPreset = StructureRepresentationPres if (!structureCell || !model) return {}; await plugin.runTask(Task.create('Validation Report', async runtime => { - await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model); + await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model); })); const colorTheme = GeometryQualityColorThemeProvider.name as any; @@ -350,7 +350,7 @@ export const ValidationReportDensityFitPreset = StructureRepresentationPresetPro if (!structureCell || !model) return {}; await plugin.runTask(Task.create('Validation Report', async runtime => { - await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model); + await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model); })); const colorTheme = DensityFitColorThemeProvider.name as any; @@ -374,7 +374,7 @@ export const ValidationReportRandomCoilIndexPreset = StructureRepresentationPres if (!structureCell || !model) return {}; await plugin.runTask(Task.create('Validation Report', async runtime => { - await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model); + await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model); })); const colorTheme = RandomCoilIndexColorThemeProvider.name as any; diff --git a/src/extensions/rcsb/validation-report/prop.ts b/src/extensions/rcsb/validation-report/prop.ts index 7e4fa8cfe..355da3089 100644 --- a/src/extensions/rcsb/validation-report/prop.ts +++ b/src/extensions/rcsb/validation-report/prop.ts @@ -10,7 +10,6 @@ import { CustomProperty } from '../../../mol-model-props/common/custom-property' import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property'; import { Model, ElementIndex, ResidueIndex } from '../../../mol-model/structure/model'; import { IntAdjacencyGraph } from '../../../mol-math/graph'; -import { readFromFile } from '../../../mol-util/data-source'; import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property'; import { InterUnitGraph } from '../../../mol-math/graph/inter-unit-graph'; import { UnitIndex } from '../../../mol-model/structure/structure/element/element'; @@ -22,6 +21,7 @@ import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif'; import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler'; import { CustomPropSymbol } from '../../../mol-script/language/symbol'; import Type from '../../../mol-script/language/type'; +import { Asset } from '../../../mol-util/assets'; export { ValidationReport }; @@ -104,20 +104,19 @@ namespace ValidationReport { return parseValidationReportXml(xml, model); } - export async function fetch(ctx: CustomProperty.Context, model: Model, props: ServerSourceProps): Promise<ValidationReport> { - const url = getEntryUrl(model.entryId, props.baseUrl); - const xml = await ctx.fetch({ url, type: 'xml' }).runInContext(ctx.runtime); - return fromXml(xml, model); + export async function fetch(ctx: CustomProperty.Context, model: Model, props: ServerSourceProps): Promise<CustomProperty.Data<ValidationReport>> { + const url = Asset.getUrlAsset(ctx.assetManager, getEntryUrl(model.entryId, props.baseUrl)); + const xml = await ctx.assetManager.resolve(url, 'xml').runInContext(ctx.runtime); + return { value: fromXml(xml.data, model), assets: [xml] }; } - export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<ValidationReport> { - // TODO: this should use the asset manager and release the file "somehow" - if (!(props.input?.file instanceof File)) throw new Error('No file given'); - const xml = await readFromFile(props.input.file, 'xml').runInContext(ctx.runtime); - return fromXml(xml, model); + export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<CustomProperty.Data<ValidationReport>> { + if (props.input === null) throw new Error('No file given'); + const xml = await ctx.assetManager.resolve(props.input, 'xml').runInContext(ctx.runtime); + return { value: fromXml(xml.data, model), assets: [xml] }; } - export async function obtain(ctx: CustomProperty.Context, model: Model, props: ValidationReportProps): Promise<ValidationReport> { + export async function obtain(ctx: CustomProperty.Context, model: Model, props: ValidationReportProps): Promise<CustomProperty.Data<ValidationReport>> { switch(props.source.name) { case 'file': return open(ctx, model, props.source.params); case 'server': return fetch(ctx, model, props.source.params); @@ -319,7 +318,9 @@ export const ClashesProvider: CustomStructureProperty.Provider<{}, Clashes> = Cu obtain: async (ctx: CustomProperty.Context, data: Structure) => { await ValidationReportProvider.attach(ctx, data.models[0]); const validationReport = ValidationReportProvider.get(data.models[0]).value!; - return createClashes(data, validationReport.clashes); + return { + value: createClashes(data, validationReport.clashes) + }; } }); diff --git a/src/mol-model-props/common/custom-element-property.ts b/src/mol-model-props/common/custom-element-property.ts index 74d9da21c..a54b47798 100644 --- a/src/mol-model-props/common/custom-element-property.ts +++ b/src/mol-model-props/common/custom-element-property.ts @@ -27,11 +27,12 @@ interface CustomElementProperty<T> { namespace CustomElementProperty { export type Value<T> = Map<ElementIndex, T> + export type Data<T> = CustomProperty.Data<Value<T>> export interface Builder<T> { label: string name: string - getData(model: Model, ctx?: CustomProperty.Context): Value<T> | Promise<Value<T>> + getData(model: Model, ctx?: CustomProperty.Context): Data<T> | Promise<Data<T>> coloring?: { getColor: (p: T) => Color defaultColor: Color diff --git a/src/mol-model-props/common/custom-model-property.ts b/src/mol-model-props/common/custom-model-property.ts index eea8c387f..2e8f14f4c 100644 --- a/src/mol-model-props/common/custom-model-property.ts +++ b/src/mol-model-props/common/custom-model-property.ts @@ -20,7 +20,7 @@ namespace CustomModelProperty { readonly defaultParams: Params readonly getParams: (data: Model) => Params readonly isApplicable: (data: Model) => boolean - readonly obtain: (ctx: CustomProperty.Context, data: Model, props: PD.Values<Params>) => Promise<Value> + readonly obtain: (ctx: CustomProperty.Context, data: Model, props: PD.Values<Params>) => Promise<CustomProperty.Data<Value>> readonly type: 'static' | 'dynamic' } @@ -56,8 +56,9 @@ namespace CustomModelProperty { const property = get(data); const p = PD.merge(builder.defaultParams, property.props, props); if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return; - const value = await builder.obtain(ctx, data, p); + const { value, assets } = await builder.obtain(ctx, data, p); data.customProperties.add(builder.descriptor); + data.customProperties.assets(builder.descriptor, assets); set(data, p, value); }, ref: (data: Model, add: boolean) => data.customProperties.reference(builder.descriptor, add), @@ -68,6 +69,8 @@ namespace CustomModelProperty { if (!PD.areEqual(builder.defaultParams, property.props, p)) { // this invalidates property.value set(data, p, undefined); + // dispose of assets + data.customProperties.assets(builder.descriptor); } }, props: (data: Model) => get(data).props, diff --git a/src/mol-model-props/common/custom-property.ts b/src/mol-model-props/common/custom-property.ts index 80e40837d..5aafd392f 100644 --- a/src/mol-model-props/common/custom-property.ts +++ b/src/mol-model-props/common/custom-property.ts @@ -9,17 +9,18 @@ import { CustomPropertyDescriptor } from '../../mol-model/structure'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { ValueBox } from '../../mol-util'; import { OrderedMap } from 'immutable'; - -type AjaxTask = import('../../mol-util/data-source').AjaxTask +import { AssetManager, Asset } from '../../mol-util/assets'; export { CustomProperty }; namespace CustomProperty { export interface Context { runtime: RuntimeContext - fetch: AjaxTask + assetManager: AssetManager } + export type Data<V> = { value: V, assets?: Asset.Wrapper[] } + export interface Container<P, V> { readonly props: P readonly data: ValueBox<V | undefined> diff --git a/src/mol-model-props/common/custom-structure-property.ts b/src/mol-model-props/common/custom-structure-property.ts index 8a5adb84b..fefa5f174 100644 --- a/src/mol-model-props/common/custom-structure-property.ts +++ b/src/mol-model-props/common/custom-structure-property.ts @@ -20,7 +20,7 @@ namespace CustomStructureProperty { readonly defaultParams: Params readonly getParams: (data: Structure) => Params readonly isApplicable: (data: Structure) => boolean - readonly obtain: (ctx: CustomProperty.Context, data: Structure, props: PD.Values<Params>) => Promise<Value> + readonly obtain: (ctx: CustomProperty.Context, data: Structure, props: PD.Values<Params>) => Promise<CustomProperty.Data<Value>> readonly type: 'root' | 'local' } @@ -58,8 +58,9 @@ namespace CustomStructureProperty { const property = get(data); const p = PD.merge(builder.defaultParams, rootProps, props); if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return; - const value = await builder.obtain(ctx, data, p); + const { value, assets } = await builder.obtain(ctx, data, p); data.customPropertyDescriptors.add(builder.descriptor); + data.customPropertyDescriptors.assets(builder.descriptor, assets); set(data, p, value); }, ref: (data: Structure, add: boolean) => data.customPropertyDescriptors.reference(builder.descriptor, add), @@ -71,6 +72,8 @@ namespace CustomStructureProperty { if (!PD.areEqual(builder.defaultParams, property.props, p)) { // this invalidates property.value set(data, p, value); + // dispose of assets + data.customPropertyDescriptors.assets(builder.descriptor); } }, props: (data: Structure) => get(data).props, diff --git a/src/mol-model-props/computed/accessible-surface-area.ts b/src/mol-model-props/computed/accessible-surface-area.ts index f691387ea..a12020eb6 100644 --- a/src/mol-model-props/computed/accessible-surface-area.ts +++ b/src/mol-model-props/computed/accessible-surface-area.ts @@ -54,6 +54,6 @@ export const AccessibleSurfaceAreaProvider: CustomStructureProperty.Provider<Acc isApplicable: (data: Structure) => true, obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AccessibleSurfaceAreaProps>) => { const p = { ...PD.getDefaultValues(AccessibleSurfaceAreaParams), ...props }; - return await AccessibleSurfaceArea.compute(data, p).runInContext(ctx.runtime); + return { value: await AccessibleSurfaceArea.compute(data, p).runInContext(ctx.runtime) }; } }); \ No newline at end of file diff --git a/src/mol-model-props/computed/interactions.ts b/src/mol-model-props/computed/interactions.ts index 88d67d898..bba29c192 100644 --- a/src/mol-model-props/computed/interactions.ts +++ b/src/mol-model-props/computed/interactions.ts @@ -30,6 +30,6 @@ export const InteractionsProvider: CustomStructureProperty.Provider<Interactions isApplicable: (data: Structure) => true, obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<InteractionsProps>) => { const p = { ...PD.getDefaultValues(InteractionsParams), ...props }; - return await computeInteractions(ctx, data, p); + return { value: await computeInteractions(ctx, data, p) }; } }); \ No newline at end of file diff --git a/src/mol-model-props/computed/secondary-structure.ts b/src/mol-model-props/computed/secondary-structure.ts index 4ca57c396..383703dfb 100644 --- a/src/mol-model-props/computed/secondary-structure.ts +++ b/src/mol-model-props/computed/secondary-structure.ts @@ -57,8 +57,8 @@ export const SecondaryStructureProvider: CustomStructureProperty.Provider<Second obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<SecondaryStructureProps>) => { const p = { ...PD.getDefaultValues(SecondaryStructureParams), ...props }; switch (p.type.name) { - case 'dssp': return await computeDssp(data, p.type.params); - case 'model': return await computeModel(data); + case 'dssp': return { value: await computeDssp(data, p.type.params) }; + case 'model': return { value: await computeModel(data) }; } } }); diff --git a/src/mol-model-props/computed/valence-model.ts b/src/mol-model-props/computed/valence-model.ts index d2994f593..8fa1d24ba 100644 --- a/src/mol-model-props/computed/valence-model.ts +++ b/src/mol-model-props/computed/valence-model.ts @@ -30,6 +30,6 @@ export const ValenceModelProvider: CustomStructureProperty.Provider<ValenceModel isApplicable: (data: Structure) => true, obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<ValenceModelProps>) => { const p = { ...PD.getDefaultValues(ValenceModelParams), ...props }; - return await calcValenceModel(ctx.runtime, data, p); + return { value: await calcValenceModel(ctx.runtime, data, p) }; } }); \ No newline at end of file diff --git a/src/mol-model-props/integrative/cross-link-restraint/property.ts b/src/mol-model-props/integrative/cross-link-restraint/property.ts index d68d94af5..8b7f07ee4 100644 --- a/src/mol-model-props/integrative/cross-link-restraint/property.ts +++ b/src/mol-model-props/integrative/cross-link-restraint/property.ts @@ -29,7 +29,7 @@ export const CrossLinkRestraintProvider: CustomStructureProperty.Provider<{}, Cr getParams: (data: Structure) => ({}), isApplicable: (data: Structure) => data.models.some(m => !!ModelCrossLinkRestraint.Provider.get(m)), obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<{}>) => { - return extractCrossLinkRestraints(data); + return { value: extractCrossLinkRestraints(data) }; } }); diff --git a/src/mol-model/structure/common/custom-property.ts b/src/mol-model/structure/common/custom-property.ts index 8f69a41d8..3c6723d76 100644 --- a/src/mol-model/structure/common/custom-property.ts +++ b/src/mol-model/structure/common/custom-property.ts @@ -1,13 +1,15 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { CifWriter } from '../../../mol-io/writer/cif'; import { CifExportContext } from '../export/mmcif'; import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler'; import { UUID } from '../../../mol-util'; +import { Asset } from '../../../mol-util/assets'; export { CustomPropertyDescriptor, CustomProperties }; @@ -42,6 +44,7 @@ class CustomProperties { private _list: CustomPropertyDescriptor[] = []; private _set = new Set<CustomPropertyDescriptor>(); private _refs = new Map<CustomPropertyDescriptor, number>(); + private _assets = new Map<CustomPropertyDescriptor, Asset.Wrapper[]>(); get all(): ReadonlyArray<CustomPropertyDescriptor> { return this._list; @@ -55,11 +58,7 @@ class CustomProperties { } reference(desc: CustomPropertyDescriptor<any>, add: boolean) { - let refs = this._refs.get(desc); - if (refs === void 0) { - refs = 0; - this._refs.set(desc, refs); - } + let refs = this._refs.get(desc) || 0; refs += add ? 1 : -1; this._refs.set(desc, Math.max(refs, 0)); } @@ -71,4 +70,21 @@ class CustomProperties { has(desc: CustomPropertyDescriptor<any>): boolean { return this._set.has(desc); } + + /** Sets assets for a prop, disposes of existing assets for that prop */ + assets(desc: CustomPropertyDescriptor<any>, assets?: Asset.Wrapper[]) { + const prevAssets = this._assets.get(desc); + if (prevAssets) { + for (const a of prevAssets) a.dispose(); + } + if (assets) this._assets.set(desc, assets); + else this._assets.delete(desc); + } + + /** Disposes of all assets of all props */ + dispose() { + this._assets.forEach(assets => { + for (const a of assets) a.dispose(); + }); + } } \ No newline at end of file diff --git a/src/mol-plugin-state/builder/structure.ts b/src/mol-plugin-state/builder/structure.ts index 322d7ea8f..ba172758e 100644 --- a/src/mol-plugin-state/builder/structure.ts +++ b/src/mol-plugin-state/builder/structure.ts @@ -173,7 +173,7 @@ export class StructureBuilder { }; if (selection.ensureCustomProperties) { - await selection.ensureCustomProperties({ fetch: this.plugin.fetch, runtime: taskCtx }, structureData); + await selection.ensureCustomProperties({ runtime: taskCtx, assetManager: this.plugin.managers.asset }, structureData); } return this.tryCreateComponent(structure, transformParams, key, tags); diff --git a/src/mol-plugin-state/helpers/structure-selection-query.ts b/src/mol-plugin-state/helpers/structure-selection-query.ts index a53303356..d374b56b8 100644 --- a/src/mol-plugin-state/helpers/structure-selection-query.ts +++ b/src/mol-plugin-state/helpers/structure-selection-query.ts @@ -73,7 +73,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S const current = plugin.managers.structure.selection.getStructure(structure); const currentSelection = current ? StructureSelection.Singletons(structure, current) : StructureSelection.Empty(structure); if (props.ensureCustomProperties) { - await props.ensureCustomProperties({ fetch: plugin.fetch, runtime }, structure); + await props.ensureCustomProperties({ runtime, assetManager: plugin.managers.asset }, structure); } if (!_query) _query = compile<StructureSelection>(expression); return _query(new QueryContext(structure, { currentSelection })); diff --git a/src/mol-plugin-state/transforms/model.ts b/src/mol-plugin-state/transforms/model.ts index 4738ae9b3..6ea005e21 100644 --- a/src/mol-plugin-state/transforms/model.ts +++ b/src/mol-plugin-state/transforms/model.ts @@ -304,6 +304,9 @@ const ModelFromTrajectory = PluginStateTransform.BuiltIn({ const label = `Model ${model.modelNum}`; const description = a.data.length === 1 ? undefined : `of ${a.data.length}`; return new SO.Molecule.Model(model, { label, description }); + }, + dispose({ b }) { + b?.data.customProperties.dispose(); } }); @@ -320,6 +323,9 @@ const StructureFromTrajectory = PluginStateTransform.BuiltIn({ const props = { label: 'Ensemble', description: Structure.elementDescription(s) }; return new SO.Molecule.Structure(s, props); }); + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } }); @@ -343,6 +349,9 @@ const StructureFromModel = PluginStateTransform.BuiltIn({ if (!b.data.models.includes(a.data)) return StateTransformer.UpdateResult.Recreate; if (!deepEqual(oldParams, newParams)) return StateTransformer.UpdateResult.Recreate; return StateTransformer.UpdateResult.Unchanged; + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } }); @@ -439,6 +448,9 @@ const TransformStructureConformation = PluginStateTransform.BuiltIn({ const s = Structure.transform(a.data, transform); return new SO.Molecule.Structure(s, { label: a.label, description: `${a.description} [Transformed]` }); + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } // interpolate(src, tar, t) { // // TODO: optimize @@ -489,6 +501,9 @@ const StructureSelectionFromExpression = PluginStateTransform.BuiltIn({ StructureQueryHelper.updateStructureObject(b, selection, newParams.label); return StateTransformer.UpdateResult.Updated; + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } }); @@ -650,6 +665,9 @@ const StructureSelectionFromScript = PluginStateTransform.BuiltIn({ const selection = StructureQueryHelper.updateStructure(entry, a.data); StructureQueryHelper.updateStructureObject(b, selection, newParams.label); return StateTransformer.UpdateResult.Updated; + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } }); @@ -698,6 +716,9 @@ const StructureSelectionFromBundle = PluginStateTransform.BuiltIn({ b.description = Structure.elementDescription(s); b.data = s; return StateTransformer.UpdateResult.Updated; + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } }); @@ -761,6 +782,9 @@ const StructureComplexElement = PluginStateTransform.BuiltIn({ if (s.elementCount === 0) return StateObject.Null; return new SO.Molecule.Structure(s, { label, description: Structure.elementDescription(s) }); + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } }); @@ -777,6 +801,9 @@ const StructureComponent = PluginStateTransform.BuiltIn({ }, update: ({ a, b, oldParams, newParams, cache }) => { return updateStructureComponent(a.data, b, oldParams, newParams, cache as any); + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } }); @@ -810,10 +837,13 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({ await attachModelProps(a.data, ctx, taskCtx, newParams); return StateTransformer.UpdateResult.Updated; }); + }, + dispose({ b }) { + b?.data.customProperties.dispose(); } }); async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomModelProperties['createDefaultParams']>) { - const propertyCtx = { runtime: taskCtx, fetch: ctx.fetch }; + const propertyCtx = { runtime: taskCtx, assetManager: ctx.managers.asset }; const { autoAttach, properties } = params; for (const name of Object.keys(properties)) { const property = ctx.customModelProperties.get(name); @@ -860,10 +890,13 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({ await attachStructureProps(a.data.root, ctx, taskCtx, newParams); return StateTransformer.UpdateResult.Updated; }); + }, + dispose({ b }) { + b?.data.customPropertyDescriptors.dispose(); } }); async function attachStructureProps(structure: Structure, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomStructureProperties['createDefaultParams']>) { - const propertyCtx = { runtime: taskCtx, fetch: ctx.fetch }; + const propertyCtx = { runtime: taskCtx, assetManager: ctx.managers.asset }; const { autoAttach, properties } = params; for (const name of Object.keys(properties)) { const property = ctx.customStructureProperties.get(name); diff --git a/src/mol-plugin-state/transforms/representation.ts b/src/mol-plugin-state/transforms/representation.ts index fb49d9b30..0aee3349c 100644 --- a/src/mol-plugin-state/transforms/representation.ts +++ b/src/mol-plugin-state/transforms/representation.ts @@ -120,7 +120,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({ }, apply({ a, params, cache }, plugin: PluginContext) { return Task.create('Structure Representation', async ctx => { - const propertyCtx = { runtime: ctx, fetch: plugin.fetch }; + const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset }; const provider = plugin.representation.structure.registry.get(params.type.name); if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data); const props = params.type.params || {}; @@ -149,7 +149,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({ Theme.releaseDependencies(plugin.representation.structure.themes, { structure: a.data }, oldParams); const provider = plugin.representation.structure.registry.get(newParams.type.name); - const propertyCtx = { runtime: ctx, fetch: plugin.fetch }; + const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset }; if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data); const props = { ...b.data.repr.props, ...newParams.type.params }; await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, newParams); @@ -524,7 +524,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({ }, apply({ a, params }, plugin: PluginContext) { return Task.create('Volume Representation', async ctx => { - const propertyCtx = { runtime: ctx, fetch: plugin.fetch }; + const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset }; const provider = plugin.representation.volume.registry.get(params.type.name); if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data); const props = params.type.params || {}; diff --git a/src/mol-util/graphql-client.ts b/src/mol-util/graphql-client.ts index 9fc1708a2..0e2f5eac7 100644 --- a/src/mol-util/graphql-client.ts +++ b/src/mol-util/graphql-client.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @@ -7,6 +7,7 @@ */ import { RuntimeContext } from '../mol-task'; +import { AssetManager, Asset } from './assets'; type Variables = { [key: string]: any } @@ -42,44 +43,33 @@ export class ClientError extends Error { this.request = request; // this is needed as Safari doesn't support .captureStackTrace - /* tslint:disable-next-line */ if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, ClientError); } } private static extractMessage (response: GraphQLResponse): string { - try { - return response.errors![0].message; - } catch (e) { - return `GraphQL Error (Code: ${response.status})`; - } + return response.errors ? response.errors[0].message : `GraphQL Error (Code: ${response.status})`; } } export class GraphQLClient { - constructor(private url: string, private fetch: import('../mol-util/data-source').AjaxTask) { - this.url = url; - } - - async request<T extends any>(ctx: RuntimeContext, query: string, variables?: Variables): Promise<T> { + constructor(private url: string, private assetManager: AssetManager) { } - const body = JSON.stringify({ - query, - variables: variables ? variables : undefined, - }); + async request(ctx: RuntimeContext, query: string, variables?: Variables): Promise<Asset.Wrapper<'json'>> { - const resultStr = await this.fetch({ url: this.url, body }).runInContext(ctx); - const result = JSON.parse(resultStr); + const body = JSON.stringify({ query, variables }, null, 2); + const url = Asset.getUrlAsset(this.assetManager, this.url, body); + const result = await this.assetManager.resolve(url, 'json').runInContext(ctx); - if (!result.errors && result.data) { - return result.data; + if (!result.data.errors && result.data.data) { + return { + data: result.data.data, + dispose: result.dispose + }; } else { - const errorResult = typeof result === 'string' ? { error: result } : result; - throw new ClientError( - { ...errorResult }, - { query, variables }, - ); + const errorResult = typeof result.data === 'string' ? { error: result.data } : result.data; + throw new ClientError({ ...errorResult }, { query, variables }); } } } \ No newline at end of file diff --git a/src/tests/browser/render-structure.ts b/src/tests/browser/render-structure.ts index 4969c910f..e57e5ba87 100644 --- a/src/tests/browser/render-structure.ts +++ b/src/tests/browser/render-structure.ts @@ -25,7 +25,7 @@ import { InteractionsRepresentationProvider } from '../../mol-model-props/comput import { InteractionsProvider } from '../../mol-model-props/computed/interactions'; import { SecondaryStructureProvider } from '../../mol-model-props/computed/secondary-structure'; import { SyncRuntimeContext } from '../../mol-task/execution/synchronous'; -import { ajaxGet } from '../../mol-util/data-source'; +import { AssetManager } from '../../mol-util/assets'; const parent = document.getElementById('app')!; parent.style.width = '100%'; @@ -117,7 +117,7 @@ function getGaussianSurfaceRepr() { } async function init() { - const ctx = { runtime: SyncRuntimeContext, fetch: ajaxGet }; + const ctx = { runtime: SyncRuntimeContext, assetManager: new AssetManager() }; const cif = await downloadFromPdb('3pqr'); const models = await getModels(cif); -- GitLab