Refresh schedule-affected tiles without full tree reload
https://github.com/orgs/iTwin/projects/37/views/26?sliceBy%5Bvalue%5D=danielzhong-bentley&pane=issue&itemId=106823705&issue=iTwin%7Citwin-graphics-backlog%7C653
Previously, when a Schedule Script changed, it would trigger a full tile tree reload by creating a new tile tree owner.
This PR attempts to refresh only the affected tiles, avoiding the need to reload the entire tile tree.
Before:
vp.displayStyle.scheduleScript = this._script;
After:
Start/Update editing mode:
vp.displayStyle.setScheduleEditingScript(this._script);
Finish and exit editing mode, everything refresh once:
vp.displayStyle.commitScheduleEditing();
TODO:
- [ ] currently only transform work, color, visibility only not updating correctly
- [ ] refine API
Please share any feedback. This is a very early version, and I want to make sure I'm heading in the right direction.
A new helper method isAffectedByScheduleScript() is added to the IModelTile class. It checks whether the tile is affected by a ScheduleScript, based on matching the model ID and element ID against the script's defined timelines.
When the tile content is detected affected, it will "refresh".
To be clear, this will not solve the problem. The use case involves a schedule script that affects most of the elements. The user is interactively, incrementally adjusting the script by e.g. adding individual elements to it. The approach you're describing will effectively refresh every tile after every little change, just like it currently does, because just about every tile is going to include at least one element that is affected by the script. What the user wants is for only those tiles that are affected by a change to the script to be refreshed. Do you have a plan for addressing that problem?
A new helper method isAffectedByScheduleScript() is added to the IModelTile class. It checks whether the tile is affected by a ScheduleScript, based on matching the model ID and element ID against the script's defined timelines.
When the tile content is detected affected, it will "refresh".
To be clear, this will not solve the problem. The use case involves a schedule script that affects most of the elements. The user is interactively, incrementally adjusting the script by e.g. adding individual elements to it. The approach you're describing will effectively refresh every tile after every little change, just like it currently does, because just about every tile is going to include at least one element that is affected by the script. What the user wants is for only those tiles that are affected by a change to the script to be refreshed. Do you have a plan for addressing that problem?
Got it. I'm planning to compare the previous and current schedule scripts to compute a delta of changed element IDs. This will allow us to identify only the elements that were added or removed from the script.
To support this, I’ll update the pruneAnimatedTiles method to accept a Set<Id64String> representing the changed element IDs. During traversal, the method will skip tiles that don't contain any of the affected elements, so we only refresh tiles that are actually impacted by the script changes.
Updated, the tiles refresh only when their corresponding elements change (e.g., visibility, color, transform). The new logic detects which elements in the schedule script need updates and refreshes only their tiles.
Please ignore the API changes, they're only for testing and will be removed before the merge.
I’ve tested the model provided by the Synchro 4D team. It’s essentially the same as the standard v1 tile .bim file, but with more models in the scene.
The main issue with the current approach is that, even though the logic is implemented at the tile class level, each model’s tile tree typically has only one large root tile.
If a model had two or more root tiles, optimization would be significantly improved. For example, if an element on the right side changes, only the right-side tiles would need to refresh. If both schedule scripts remain unchanged, then no refresh would be needed at all.
However, because most models only have a single large root tile, detecting a change at the root level ends up same as triggering a refresh of the entire tile tree/tree owner. All models within that tile are refreshed unnecessarily.
I'm trying to implement a method that detects only the current camera level, so the root tile is refreshed only when zooming out. If the camera isn't zoomed out, only the child tiles at the current level would be refreshed.
However, if the camera is positioned far away, the same issue mentioned earlier still occurs — the root tile ends up being refreshed, causing the everything reload unnecessarily(if the tile tree only has 1 root tile).
I'm trying to implement a method that detects only the current camera level, so the root tile is refreshed only when zooming out. If the camera isn't zoomed out, only the child tiles at the current level would be refreshed.
So just to be clear, all tiles on screen get refreshed anyway because of single root? If zoomed in, the entire tree won't get refreshed, but tiles on current zoom level (all tiles on screen) would refresh then, and if zoomed out, then same effect basically, but since entire tree is visible on screen, so root and all children get refreshed?
If I understand correctly then, while a potential gain in performance when zoomed in, would there be visual difference to flickering (loading tiles) while refreshing those tiles?
If I understand correctly then, while a potential gain in performance when zoomed in, would there be visual difference to flickering (loading tiles) while refreshing those tiles?
It could be made to limit visible flickering/refresh but only to a point. With this approach, any tile that does contain at least one element affected by a change to the timeline is going to have to update the content for all of the elements it contains.
Your problem is similar to that faced by authoring apps: the user is making interactive, incremental changes that affect individual elements' visualization and want to see the visualization update in real time. Authoring apps achieve that using GraphicalEditingScope. I think it would be productive to revisit a previous suggestion I made:
- Permit the
timelineon the display style and tile tree Id to be an object representing a "timeline under construction" that emits notifications when the contents of the timeline change. - Leverage dynamic tiles to hide elements affected by timeline changes in the tiles and draw them using temporary graphics instead.
- Ensure those graphics obey the timeline (transform, clip, color, visibility).
- Permit the application to notify us when modifications to the timeline are complete, so we can discard the temporary graphics and repair the tiles. This can be important for performance when the user accumulates changes to a very large number of elements.
How much implementation could be shared between this approach and GraphicalEditingScope is TBD. GraphicalEditingScope involves coordination between backend and frontend whereas timeline modification is frontend-only and doesn't require a writable iModel. You could imagine a scenario where an app is both modifying elements and modifying the timeline, where the two would need to cooperate, but I don't know if @gedkek has that use case.
Ready for another round of feedback.
Currently, the implementation follows the idea described above: The tile tree owner creates a new tile tree only after the timeline editing is finished. While editing, it transitions to the Dynamic tile and hides the static tile.
It works perfectly for the script contains transform. However, there’s a problem when changes are purely non-transformational (such as color, visibility, or cutting plane changes). In these cases, transitioning to the Dynamic tile hides the static tile, but nothing appears in its place.
As a temporary workaround, I simply manually added this line for schedule script:
elementTimeline1.addTransform(now, Transform.createIdentity());
Ready to Review. Everything is working as intended, including both transform and non-transform changes. Looking for feedback on how to refactor the code better
Editing Mode with color chage:
https://github.com/user-attachments/assets/a0571c65-9d46-4a39-8410-a8ba89e043ab
Editing Mode with Transform change:
https://github.com/user-attachments/assets/67c40a43-70fb-41c2-ae06-9a69ec67fba3
Finish editing (everything refresh once for final)
https://github.com/user-attachments/assets/d044d9c7-8149-449d-8073-30e8abfcaaa1
Everything is working as intended, including both transform and non-transform changes. Looking for feedback on how to refactor the code better
Number one priority: add unit tests to prove everything works as intended, today and into the future.
Added unit tests and confirmed image test is working. Will extract the API after the review, because temporary APIs were added to simplify testing the schedule script, they will be removed.
Please make the builds pass so we can verify tests etc.
Does this work with both V1 and V4 tiles?
Not yet, it currently only supports v1 tiles.
Please provide a code snippet (in example-code-snippets) illustrating how someone would use this API to make multiple incremental changes to a schedule script and then commit the finished script.
Updated the description to include code snippets.
Have you asked the people who want this enhancement whether or not they are using or plan to use V4 tiles?
Have you asked the people who want this enhancement whether or not they are using or plan to use V4 tiles?
They weren’t familiar with the V1 or V4 tiles the last time I reached out, but I’ll double check with them.
Have you asked the people who want this enhancement whether or not they are using or plan to use V4 tiles?
They mentioned they’re unsure about any plans to use V4 tiles and will go with whatever is set as the default.