Skip to content

Commit 1fde03c

Browse files
committed
feat(ShareDataSet): initial implementation
1 parent 4fb11dd commit 1fde03c

File tree

5 files changed

+253
-13
lines changed

5 files changed

+253
-13
lines changed

src/core-ts/GeometryRepresentation.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,8 @@ export default forwardRef(function GeometryRepresentation(
254254
}
255255
});
256256

257-
const representation = useMemo<IRepresentation<vtkActor, vtkMapper>>(
257+
const representation = useMemo<IRepresentation>(
258258
() => ({
259-
getActor,
260-
getMapper,
261259
dataChanged: () => {
262260
view.requestRender();
263261
},
@@ -266,7 +264,7 @@ export default forwardRef(function GeometryRepresentation(
266264
representation.dataChanged();
267265
},
268266
}),
269-
[view, getActor, getMapper]
267+
[view]
270268
);
271269

272270
useImperativeHandle(fwdRef, () => representation);

src/core-ts/ShareDataSet.tsx

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/**
2+
* <ShareDataSetRoot>
3+
* <RegisterDataSet id="something">
4+
* </RegisterDataSet>
5+
* <Representation>
6+
* <UseDataSet id="something">
7+
* </Rep>
8+
* </ShareDataSetRoot>
9+
*/
10+
11+
import { vtkObject } from '@kitware/vtk.js/interfaces';
12+
import {
13+
PropsWithChildren,
14+
useCallback,
15+
useEffect,
16+
useMemo,
17+
useRef,
18+
} from 'react';
19+
import {
20+
DataCallback,
21+
IDownstream,
22+
IRepresentation,
23+
IShareDataset,
24+
} from '../types';
25+
import useUnmount from '../utils-ts/useUnmount';
26+
import {
27+
DownstreamContext,
28+
RepresentationContext,
29+
ShareDataSetContext,
30+
useDownstream,
31+
useRepresentation,
32+
useShareDataSet,
33+
} from './contexts';
34+
35+
const DATA_AVAILABLE_EVENT = 'dataAvailable';
36+
const DATA_CHANGED_EVENT = 'dataChanged';
37+
38+
interface DataEventDetails {
39+
name: string;
40+
}
41+
42+
export function ShareDataSetRoot(props: PropsWithChildren) {
43+
const datasets = useRef<Map<string, unknown>>(new Map());
44+
const eventTarget = useRef(new EventTarget());
45+
// Used to notify UseDataSet components that register
46+
// after the DATA_AVAILABLE_EVENT occurs.
47+
const dataAvailable = useRef(new Set<string>());
48+
49+
useUnmount(() => {
50+
datasets.current.clear();
51+
dataAvailable.current.clear();
52+
});
53+
54+
type RegistrarOptions = {
55+
immediate: boolean | ((name: string) => boolean);
56+
};
57+
58+
const createDataEventRegistrar = useCallback(
59+
(event: string, options?: RegistrarOptions) => {
60+
return function addListener(name: string, callback: DataCallback) {
61+
const invoke = () => {
62+
callback(datasets.current.get(name) ?? null);
63+
};
64+
65+
const handler = (ev: Event) => {
66+
if (!(ev instanceof CustomEvent<DataEventDetails>)) return;
67+
if (name === ev.detail?.name) {
68+
invoke();
69+
}
70+
};
71+
72+
let immediate = false;
73+
if (typeof options?.immediate === 'boolean') {
74+
immediate = options.immediate;
75+
} else if (options?.immediate instanceof Function) {
76+
immediate = options?.immediate(name);
77+
}
78+
79+
if (immediate) {
80+
invoke();
81+
}
82+
83+
eventTarget.current.addEventListener(event, handler);
84+
return () => {
85+
eventTarget.current.removeEventListener(event, handler);
86+
};
87+
};
88+
},
89+
[]
90+
);
91+
92+
const share = useMemo<IShareDataset>(
93+
() => ({
94+
register(name: string, dataset: unknown) {
95+
datasets.current.set(name, dataset);
96+
},
97+
unregister(name: string) {
98+
datasets.current.delete(name);
99+
dataAvailable.current.delete(name);
100+
},
101+
102+
dispatchDataAvailable(name: string) {
103+
dataAvailable.current.add(name);
104+
eventTarget.current.dispatchEvent(
105+
new CustomEvent<DataEventDetails>(DATA_AVAILABLE_EVENT, {
106+
detail: { name },
107+
})
108+
);
109+
},
110+
dispatchDataChanged(name: string) {
111+
eventTarget.current.dispatchEvent(
112+
new CustomEvent<DataEventDetails>(DATA_CHANGED_EVENT, {
113+
detail: { name },
114+
})
115+
);
116+
},
117+
118+
onDataChanged: createDataEventRegistrar(DATA_CHANGED_EVENT),
119+
onDataAvailable: createDataEventRegistrar(DATA_AVAILABLE_EVENT, {
120+
immediate(name) {
121+
return dataAvailable.current.has(name);
122+
},
123+
}),
124+
}),
125+
[createDataEventRegistrar]
126+
);
127+
128+
return (
129+
<ShareDataSetContext.Provider value={share}>
130+
{props.children}
131+
</ShareDataSetContext.Provider>
132+
);
133+
}
134+
135+
interface RegisterDataSetProps extends PropsWithChildren {
136+
id: string;
137+
}
138+
139+
export function RegisterDataSet(props: RegisterDataSetProps) {
140+
const share = useShareDataSet();
141+
const { id } = props;
142+
143+
// --- handle registrations --- //
144+
145+
useEffect(
146+
() => () => {
147+
share.unregister(id);
148+
},
149+
[id, share]
150+
);
151+
152+
useUnmount(() => {
153+
share.unregister(id);
154+
});
155+
156+
// --- //
157+
158+
const api = useMemo<IDownstream>(
159+
() => ({
160+
setInputData(obj) {
161+
share.register(id, obj);
162+
},
163+
setInputConnection(conn) {
164+
api.setInputData(conn());
165+
},
166+
}),
167+
[id, share]
168+
);
169+
170+
const getAPI = useCallback(() => api, [api]);
171+
const mockRepresentation = useMemo<IRepresentation>(
172+
() => ({
173+
dataChanged() {
174+
share.dispatchDataChanged(id);
175+
},
176+
dataAvailable() {
177+
share.dispatchDataAvailable(id);
178+
},
179+
}),
180+
[id, share]
181+
);
182+
183+
return (
184+
<RepresentationContext.Provider value={mockRepresentation}>
185+
<DownstreamContext.Provider value={getAPI}>
186+
{props.children}
187+
</DownstreamContext.Provider>
188+
</RepresentationContext.Provider>
189+
);
190+
}
191+
192+
interface UseDataSetProps extends PropsWithChildren {
193+
id: string;
194+
port?: number;
195+
}
196+
197+
export function UseDataSet(props: UseDataSetProps) {
198+
const { id, port = 0 } = props;
199+
const share = useShareDataSet();
200+
// TODO if useDataSet is input to an algorithm, should representation be null?
201+
const representation = useRepresentation();
202+
const getDownstream = useDownstream();
203+
204+
useEffect(() => {
205+
return share.onDataAvailable(id, (ds) => {
206+
getDownstream().setInputData(ds as vtkObject, port);
207+
representation.dataAvailable();
208+
});
209+
}, [id, port, representation, getDownstream, share]);
210+
211+
useEffect(() => {
212+
return share.onDataChanged(id, () => {
213+
representation.dataChanged();
214+
});
215+
}, [id, representation, share]);
216+
217+
return null;
218+
}

src/core-ts/contexts.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import vtkDataSetAttributes from '@kitware/vtk.js/Common/DataModel/DataSetAttrib
33
import vtkFieldData from '@kitware/vtk.js/Common/DataModel/DataSetAttributes/FieldData';
44
import { Nullable } from '@kitware/vtk.js/types';
55
import { createContext, useContext } from 'react';
6-
import { IDataset, IDownstream, IRepresentation, IView } from '../types';
6+
import {
7+
IDataset,
8+
IDownstream,
9+
IRepresentation,
10+
IShareDataset,
11+
IView,
12+
} from '../types';
713

814
export const FieldDataContext =
915
createContext<Nullable<() => vtkDataSetAttributes | vtkFieldData>>(null);
@@ -46,3 +52,11 @@ export function useDownstream() {
4652
if (!ds) throw new Error('No downstream context!');
4753
return ds;
4854
}
55+
56+
export const ShareDataSetContext = createContext<Nullable<IShareDataset>>(null);
57+
58+
export function useShareDataSet() {
59+
const share = useContext(ShareDataSetContext);
60+
if (!share) throw new Error('No ShareDataSet context!');
61+
return share;
62+
}

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@ export { default as DataArray } from './core-ts/DataArray';
88
export { default as GeometryRepresentation } from './core-ts/GeometryRepresentation';
99
export { default as PointData } from './core-ts/PointData';
1010
export { default as PolyData } from './core-ts/PolyData';
11+
export {
12+
RegisterDataSet,
13+
ShareDataSetRoot,
14+
UseDataSet,
15+
} from './core-ts/ShareDataSet';
1116
export { default as SliceRepresentation } from './core-ts/SliceRepresentation';
1217
export { default as View } from './core-ts/View';

src/types/index.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { vtkObject } from '@kitware/vtk.js/interfaces';
2-
import vtkAbstractMapper from '@kitware/vtk.js/Rendering/Core/AbstractMapper';
32
import vtkCamera from '@kitware/vtk.js/Rendering/Core/Camera';
4-
import vtkProp from '@kitware/vtk.js/Rendering/Core/Prop';
53
import vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer';
64
import vtkRenderWindow from '@kitware/vtk.js/Rendering/Core/RenderWindow';
75
import vtkRenderWindowInteractor from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor';
@@ -46,14 +44,9 @@ export interface IView {
4644
resetCamera(boundsToUse?: Bounds): void;
4745
}
4846

49-
export interface IRepresentation<
50-
A extends vtkProp = vtkProp,
51-
M extends vtkAbstractMapper = vtkAbstractMapper
52-
> {
47+
export interface IRepresentation {
5348
dataAvailable(): void;
5449
dataChanged(): void;
55-
getActor(): A;
56-
getMapper(): M;
5750
}
5851

5952
// There is no sufficient type that overlaps classes like
@@ -67,3 +60,15 @@ export interface IDataset<T> {
6760
getDataSet(): T;
6861
modified(): void;
6962
}
63+
64+
export type StopEventListener = () => void;
65+
export type DataCallback = <T>(ds: T | null) => void;
66+
67+
export interface IShareDataset {
68+
register(name: string, dataset: unknown): void;
69+
unregister(name: string): void;
70+
dispatchDataAvailable(name: string): void;
71+
dispatchDataChanged(name: string): void;
72+
onDataAvailable(name: string, callback: DataCallback): StopEventListener;
73+
onDataChanged(name: string, callback: DataCallback): StopEventListener;
74+
}

0 commit comments

Comments
 (0)