Skip to content

Commit 67cd840

Browse files
committed
fix: prevent concurrent update
1 parent 1efa6b4 commit 67cd840

File tree

4 files changed

+148
-87
lines changed

4 files changed

+148
-87
lines changed

examples/vtk/cone.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from trame.app import get_server
22
from trame.ui.html import DivLayout
3-
from trame.widgets import html
3+
from trame.widgets import html, client
44
from trame_vtklocal.widgets import vtklocal
55
from trame.decorators import TrameApp, change
66

@@ -58,6 +58,7 @@ class DemoApp:
5858
def __init__(self, server=None):
5959
self.server = get_server(server, client_type=CLIENT_TYPE)
6060
self.render_window, self.cone = create_vtk_pipeline()
61+
self.server.state.update(dict(mem_blob=0, mem_vtk=0))
6162
self.html_view = None
6263
self.ui = self._ui()
6364
print(self.ui)
@@ -71,8 +72,16 @@ def on_resolution_change(self, resolution, **kwargs):
7172

7273
def _ui(self):
7374
with DivLayout(self.server) as layout:
75+
client.Style("body { margin: 0; }")
7476
self.html_view = vtklocal.LocalView(
75-
self.render_window, style="width: 100vw; height: 100vh;"
77+
self.render_window,
78+
style="width: 100vw; height: 100vh;",
79+
cache_size=("cache", 0),
80+
memory="mem_blob = $event.blobs; mem_vtk = $event.scene;",
81+
)
82+
html.Div(
83+
"Scene: {{ (mem_vtk / 1024).toFixed(1) }}KB - Arrays: {{ (mem_blob / 1024).toFixed(1) }}KB - cache: {{ (cache/1024).toFixed(1) }}KB ",
84+
style="position: absolute; top: 1rem; left: 1rem; z-index: 10; background: white; padding: 1rem; border-radius: 1rem;",
7685
)
7786
html.Input(
7887
type="range",
@@ -82,6 +91,14 @@ def _ui(self):
8291
step=1,
8392
style="position: absolute; top: 1rem; right: 1rem; z-index: 10;",
8493
)
94+
html.Input(
95+
type="range",
96+
v_model=("cache", 0),
97+
min=0,
98+
max=100000,
99+
step=1000,
100+
style="position: absolute; top: 1rem; right: 10rem; z-index: 10;",
101+
)
85102

86103
return layout
87104

examples/vtk/glyph.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from trame.widgets import vuetify3 as vuetify
44
from trame.widgets.vtk import VtkRemoteView
55
from trame_vtklocal.widgets import vtklocal
6+
from trame.decorators import TrameApp, change
67

78
from vtkmodules.vtkCommonColor import vtkNamedColors
89
from vtkmodules.vtkFiltersCore import vtkElevationFilter, vtkGlyph3D
910
from vtkmodules.vtkFiltersSources import vtkConeSource, vtkCubeSource, vtkSphereSource
1011
from vtkmodules.vtkImagingCore import vtkRTAnalyticSource
1112
from vtkmodules.vtkImagingGeneral import vtkImageGradient
12-
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
1313
from vtkmodules.vtkRenderingCore import (
1414
vtkActor,
1515
vtkPolyDataMapper,
@@ -98,38 +98,56 @@ def setup_vtk():
9898

9999
renWin = vtkRenderWindow()
100100
renWin.AddRenderer(ren)
101-
renWin.SetWindowName("GlyphTable")
102101

103-
iren = vtkRenderWindowInteractor()
104-
istyle = vtkInteractorStyleTrackballCamera()
105-
iren.SetInteractorStyle(istyle)
106-
iren.SetRenderWindow(renWin)
102+
renderWindowInteractor = vtkRenderWindowInteractor()
103+
renderWindowInteractor.SetRenderWindow(renWin)
104+
renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
105+
107106
ren.ResetCamera()
108-
return renWin, cs2, ss
107+
108+
return renWin, ren, cs2, ss
109109

110110

111111
# -----------------------------------------------------------------------------
112112
# GUI
113113
# -----------------------------------------------------------------------------
114114

115115

116+
@TrameApp()
116117
class App:
117118
def __init__(self, server=None):
118119
self.server = get_server(server, client_type="vue3")
119-
self.render_window, self.cone, self.sphere = setup_vtk()
120+
self.render_window, self.renderer, self.cone, self.sphere = setup_vtk()
121+
self.view_local = None
122+
self.view_remote = None
120123
self.ui = self._build_ui()
121124

122125
@property
123126
def ctrl(self):
124127
return self.server.controller
125128

129+
@change("resolution")
130+
def on_resolution_change(self, resolution, **kwargs):
131+
self.cone.SetResolution(int(resolution))
132+
self.sphere.SetStartTheta(int(resolution) * 6)
133+
self.view_local.update()
134+
self.view_remote.update()
135+
136+
def reset_camera(self):
137+
self.renderer.ResetCamera()
138+
self.view_local.update()
139+
self.view_remote.update()
140+
126141
def _build_ui(self):
127142
with SinglePageLayout(self.server) as layout:
128-
layout.icon.click = self.ctrl.view_reset_camera
143+
layout.icon.click = self.reset_camera
129144
with layout.toolbar:
130145
vuetify.VSpacer()
131146
vuetify.VSlider(
132147
v_model=("resolution", 6),
148+
min=3,
149+
max=60,
150+
step=1,
133151
dense=True,
134152
hide_details=True,
135153
)
@@ -143,12 +161,14 @@ def _build_ui(self):
143161
with vuetify.VContainer(
144162
fluid=True, classes="pa-0 fill-height", style="width: 50%;"
145163
):
146-
view = vtklocal.LocalView(self.render_window)
147-
self.ctrl.view_update = view.update
164+
self.view_local = vtklocal.LocalView(self.render_window)
165+
self.ctrl.view_update = self.view_local.update
148166
with vuetify.VContainer(
149167
fluid=True, classes="pa-0 fill-height", style="width: 50%;"
150168
):
151-
VtkRemoteView(self.render_window)
169+
self.view_remote = VtkRemoteView(
170+
self.render_window, interactive_ratio=1
171+
)
152172

153173
# hide footer
154174
layout.footer.hide()

trame_vtklocal/widgets/vtklocal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ def __init__(self, render_window, **kwargs):
3434

3535
self._attributes["rw_id"] = f'render-window="{self._window_id}"'
3636
self._attributes["ref"] = f'ref="{self.__ref}"'
37-
self._attr_names += []
38-
self._event_names += []
37+
self._attr_names += [
38+
("cache_size", "cacheSize"),
39+
]
40+
self._event_names += ["updated", "memory"]
3941

4042
@property
4143
def api(self):

vue-components/src/components/VtkLocal.js

Lines changed: 93 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ export default {
2626
const hashesMTime = {};
2727
const currentMTime = ref(1);
2828
let objectManager = null;
29+
let updateInProgress = 0;
2930

3031
function resize() {
3132
const { width, height } = container.value.getBoundingClientRect();
3233
const w = Math.floor(width * window.devicePixelRatio + 0.5);
3334
const h = Math.floor(height * window.devicePixelRatio + 0.5);
3435
canvasWidth.value = w;
3536
canvasHeight.value = h;
36-
console.log(`vtkLocal::resize ${width}x${height}`);
37+
// console.log(`vtkLocal::resize ${width}x${height}`);
3738
if (props.renderWindow.length > 0) {
3839
nextTick(() => {
3940
objectManager.setSize(props.renderWindow, w, h);
@@ -48,102 +49,123 @@ export default {
4849
const serverState = await session.call("vtklocal.get.state", [vtkId]);
4950
if (serverState.length > 0) {
5051
stateMTimes[vtkId] = JSON.parse(serverState).MTime;
51-
console.log(`vtkLocal::state(${vtkId})`);
52+
// console.log(`vtkLocal::state(${vtkId})`);
5253
objectManager.registerState(serverState);
5354
} else {
54-
throw new Error(`Server returned empty state for ${vtkId}`);
55+
console.log(`Server returned empty state for ${vtkId}`);
56+
// throw new Error(`Server returned empty state for ${vtkId}`);
5557
}
5658
return serverState;
5759
}
5860
async function fetchHash(hash) {
5961
const session = client.getConnection().getSession();
62+
// console.log(`vtkLocal::hash(${hash}) - before`);
6063
const blob = await session.call("vtklocal.get.hash", [hash]);
61-
console.log(`vtkLocal::hash(${hash})`);
6264
const array = new Uint8Array(await blob.arrayBuffer());
6365
objectManager.registerBlob(hash, array);
66+
// console.log(`vtkLocal::hash(${hash}) - available`);
6467
hashesMTime[hash] = unref(currentMTime);
6568
return blob;
6669
}
6770

6871
async function update(startEventLoop = false) {
69-
console.log("vtkLocal::update(begin)");
70-
const session = client.getConnection().getSession();
71-
const serverStatus = await session.call("vtklocal.get.status", [
72-
props.renderWindow,
73-
]);
74-
const pendingRequests = [];
75-
console.log("ids", serverStatus.ids);
76-
serverStatus.ids.forEach(([vtkId, mtime]) => {
77-
if (!stateMTimes[vtkId] || stateMTimes[vtkId] < mtime) {
78-
console.log("fetch", vtkId);
79-
pendingRequests.push(fetchState(vtkId));
80-
} else {
81-
console.log("skip", vtkId);
82-
}
83-
});
84-
serverStatus.ignore_ids.forEach((vtkId) => {
85-
objectManager.unRegisterState(vtkId);
86-
});
87-
serverStatus.hashes.forEach((hash) => {
88-
if (!hashesMTime[hash]) {
89-
pendingRequests.push(fetchHash(hash));
90-
}
91-
hashesMTime[hash] = unref(currentMTime);
92-
});
93-
await Promise.all(pendingRequests);
94-
// Shows memory, feel free to remove.
95-
console.log(
96-
`vtkLocal::update(end) blobs: ${
97-
objectManager.getTotalBlobMemoryUsage() / 1024
98-
}kB, objects: ${
99-
objectManager.getTotalVTKDataObjectMemoryUsage() / 1024
100-
}kB`
101-
);
102-
objectManager.update(startEventLoop);
103-
resize();
104-
emit("updated");
72+
updateInProgress++;
73+
if (updateInProgress !== 1) {
74+
// console.error("Skip concurrent update");
75+
return;
76+
}
10577

106-
// Memory management
107-
currentMTime.value++;
108-
const threshold =
109-
props.cacheSize + objectManager.getTotalVTKDataObjectMemoryUsage();
110-
if (objectManager.getTotalBlobMemoryUsage() > threshold) {
111-
// Need to remove old blobs
112-
const threshold =
113-
props.cacheSize + objectManager.getTotalVTKDataObjectMemoryUsage();
114-
const tsMap = {};
115-
let mtimeToFree = unref(currentMTime);
116-
Object.entries(hashesMTime).forEach(([hash, mtime]) => {
117-
if (mtime < mtimeToFree) {
118-
mtimeToFree = mtime;
119-
}
120-
const sMtime = mtime.toString();
121-
if (tsMap[sMtime]) {
122-
tsMap[sMtime].push(hash);
78+
try {
79+
// console.log("vtkLocal::update(begin)");
80+
const session = client.getConnection().getSession();
81+
const serverStatus = await session.call("vtklocal.get.status", [
82+
props.renderWindow,
83+
]);
84+
const pendingRequests = [];
85+
// console.log("ids", serverStatus.ids);
86+
serverStatus.ids.forEach(([vtkId, mtime]) => {
87+
if (!stateMTimes[vtkId] || stateMTimes[vtkId] < mtime) {
88+
// console.log("fetch", vtkId);
89+
pendingRequests.push(fetchState(vtkId));
12390
} else {
124-
tsMap[sMtime] = [hash];
91+
// console.log("skip", vtkId);
92+
}
93+
});
94+
serverStatus.ignore_ids.forEach((vtkId) => {
95+
objectManager.unRegisterState(vtkId);
96+
});
97+
serverStatus.hashes.forEach((hash) => {
98+
if (!hashesMTime[hash]) {
99+
pendingRequests.push(fetchHash(hash));
125100
}
101+
hashesMTime[hash] = unref(currentMTime);
126102
});
103+
await Promise.all(pendingRequests);
104+
try {
105+
objectManager.update(startEventLoop);
106+
} catch (e) {
107+
console.error("WASM update failed");
108+
console.log(e);
109+
}
110+
resize();
111+
emit("updated");
127112

128-
// Remove blobs starting by the old ones
129-
while (objectManager.getTotalBlobMemoryUsage() > threshold) {
130-
const hashesToDelete = tsMap[mtimeToFree];
131-
for (let i = 0; i < hashesToDelete.length; i++) {
132-
objectManager.unRegisterBlob(hashesToDelete[i]);
113+
// Memory management
114+
currentMTime.value++;
115+
const threshold =
116+
Number(props.cacheSize) +
117+
objectManager.getTotalVTKDataObjectMemoryUsage();
118+
if (objectManager.getTotalBlobMemoryUsage() > threshold) {
119+
// console.log("Free memory");
120+
// Need to remove old blobs
121+
const tsMap = {};
122+
let mtimeToFree = unref(currentMTime);
123+
Object.entries(hashesMTime).forEach(([hash, mtime]) => {
124+
if (mtime < mtimeToFree) {
125+
mtimeToFree = mtime;
126+
}
127+
const sMtime = mtime.toString();
128+
if (tsMap[sMtime]) {
129+
tsMap[sMtime].push(hash);
130+
} else {
131+
tsMap[sMtime] = [hash];
132+
}
133+
});
134+
135+
// Remove blobs starting by the old ones
136+
while (objectManager.getTotalBlobMemoryUsage() > threshold) {
137+
const hashesToDelete = tsMap[mtimeToFree];
138+
if (hashesToDelete) {
139+
for (let i = 0; i < hashesToDelete.length; i++) {
140+
// console.log(
141+
// `Delete hash(${hashesToDelete[i]}) - mtime: ${mtimeToFree}`
142+
// );
143+
objectManager.unRegisterBlob(hashesToDelete[i]);
144+
delete hashesMTime[hashesToDelete[i]];
145+
}
146+
}
147+
mtimeToFree++;
133148
}
134-
mtimeToFree++;
149+
}
150+
emit("memory", {
151+
blobs: objectManager.getTotalBlobMemoryUsage(),
152+
scene: objectManager.getTotalVTKDataObjectMemoryUsage(),
153+
});
154+
} catch (e) {
155+
console.error("Error in update", e);
156+
} finally {
157+
updateInProgress--;
158+
if (updateInProgress) {
159+
updateInProgress = 0;
160+
update();
135161
}
136162
}
137-
emit("memory", {
138-
blobs: objectManager.getTotalBlobMemoryUsage(),
139-
scene: objectManager.getTotalVTKDataObjectMemoryUsage(),
140-
});
141163
}
142164

143165
onMounted(async () => {
144-
console.log("vtkLocal::mounted");
166+
// console.log("vtkLocal::mounted");
145167
objectManager = await createModule(unref(canvas));
146-
console.log("objectManager", objectManager);
168+
// console.log("objectManager", objectManager);
147169
resizeObserver.observe(unref(container));
148170
update(/*startEventLoop=*/ true);
149171
setTimeout(() => {
@@ -154,7 +176,7 @@ export default {
154176
});
155177

156178
onBeforeUnmount(() => {
157-
console.log("vtkLocal::unmounted");
179+
// console.log("vtkLocal::unmounted");
158180
if (resizeObserver) {
159181
resizeObserver.disconnect();
160182
resizeObserver = null;

0 commit comments

Comments
 (0)