-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Add caching to sampleTerrain #6284
Conversation
@hpinkos, thanks for the pull request! Maintainers, we have a signed CLA from @hpinkos, so you can review this at any time.
I am a bot who helps you make Cesium awesome! Contributions to my configuration are welcome. 🌍 🌎 🌏 |
Here's some sample code I used for testing this: var worldTerrain = Cesium.createWorldTerrain({
requestWaterMask: true,
requestVertexNormals: true
});
var viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: worldTerrain
});
Sandcastle.addToolbarButton('Sample', function() {
Cesium.sampleTerrainMostDetailed(worldTerrain, [Cesium.Cartographic.fromDegrees(40, 40)]);
});
Sandcastle.addToolbarButton('Sample random', function() {
Cesium.sampleTerrainMostDetailed(worldTerrain, [Cesium.Cartographic.fromDegrees(Math.random() * 80, Math.random() * 80)]);
}); |
This is ready for review |
I'll start taking a look. |
Source/Core/DoublyLinkedList.js
Outdated
|
||
var i; | ||
var node = this.head; | ||
for (i = 0; i < startIndex; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style suggestions:
Remove var i;
and declare it in the loop.
Change i++
to ++i
Source/Core/LRUCache.js
Outdated
requestAnimationFrame, | ||
DeveloperError, | ||
DoublyLinkedList) { | ||
'use strict'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix indentation for includes.
Source/Core/LRUCache.js
Outdated
'./defineProperties', | ||
'./getTimestamp', | ||
'./requestAnimationFrame', | ||
'./DeveloperError', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor point, but requestAnimationFrame
isn't in alphabetical order.
Source/Core/sampleTerrain.js
Outdated
requestPromise = tileRequest.terrainProvider.requestTileGeometry(tileRequest.x, tileRequest.y, tileRequest.level); | ||
cache.set(cacheKey, requestPromise); | ||
} | ||
var tilePromise = when(requestPromise) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is when(
needed?
Specs/Core/LRUCacheSpec.js
Outdated
], function( | ||
LRUCache, | ||
getTimestamp) { | ||
'use strict'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix indentation for includes.
Source/Core/LRUCache.js
Outdated
* @alias AssociativeArray | ||
* @constructor | ||
*/ | ||
function LRUCache(capacity, expiration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually not sure.. in Cesium is the preferred style to put the constructor first always or okay to have helpers above it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've always done it this way.
But I guess it does say constructor first in the style guide: https://github.com/AnalyticalGraphicsInc/cesium/tree/master/Documentation/Contributors/CodingGuide#put-the-constructor-function-at-the-top-of-the-file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, good to know.
It looks like DoublyLinkedList
was fixed but not LRUCache
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops! All fixed now
Source/Core/LRUCache.js
Outdated
* A cache for storing key-value pairs | ||
* @param {Number} [capacity] The capacity of the cache. If undefined, the size will be unlimited. | ||
* @param {Number} [expiration] The number of milliseconds before an item in the cache expires and will be discarded when LRUCache.prune is called. If undefined, items do not expire. | ||
* @alias AssociativeArray |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AssociativeArray
-> LRUCache
Source/Core/LRUCache.js
Outdated
* A cache for storing key-value pairs | ||
* @param {Number} [capacity] The capacity of the cache. If undefined, the size will be unlimited. | ||
* @param {Number} [expiration] The number of milliseconds before an item in the cache expires and will be discarded when LRUCache.prune is called. If undefined, items do not expire. | ||
* @alias AssociativeArray |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this class be private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It probably doesn't have to be, but it isn't really useful in the Cesium API so I decided to make it private
Source/Core/LRUCache.js
Outdated
prune(cache); | ||
|
||
if (cache.length > 0) { | ||
requestAnimationFrame(loop); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that it's good practice to call requestAnimationFrame
here or anywhere besides the main render loop in CesiumWidget
. I don't think it will break anything, but it may request a render that isn't desired.
At the same time, I'm not sure what the alternative would be. Is there any way for the lru cache to be owned by something with access to frame state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally had Scene.update
tell sampleTerrain
to call cache.checkExpiration
, but that results in that function being called every frame. This way, LRUCache can manage it's own expiration and doesn't execute the loop when there's nothing in the cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that LRUCache
is cleaner when it manages itself, I'm just worried about the calls to requestAnimationFrame
.
Would it be not completely terrible to call prune
after each set
call? Or maybe use setTimeout
if it doesn't need to prune every frame?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer what @hpinkos has done here. It will not trigger a Cesium render, it will simply let LRUcache does it's processing all at once on the next browser (not Cesium) render
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern is that it will trigger a browser render unnecessarily. Or maybe it won't... maybe that's going to happen every frame regardless. But I still think requestAnimationFrame
really only belongs inside the main render loop since that's where we "ask" for a frame. But if this is the cleanest way to get the job done, then I suppose it's ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two week later update. I read up on requestAnimationFrame
more and it seems that it is purely a callback aggregator and it won't force a render like I thought might be happening. Sorry if this is already common knowledge...
I still think its nice that requestAnimationFrame
is called in just one place, but technically it should be fine to call in multiple.
https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animation-frames
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the render loop, I agree incompletely, there should only be one requestAnimationFrame
call. For anything we want to have happen independent of the render loop, multiple requestAnimationFrame
make total sense. I think we need to continue to decouple things from our actual webgl render loop and move them one level up into requestAnimationFrame
.
Specs/Core/LRUCacheSpec.js
Outdated
}); | ||
|
||
it('set throws with undefined key', function() { | ||
var associativeArray = new LRUCache(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to cache
.
@lilleyse ready for another look |
@lilleyse anything else? |
Nope that's it, thanks @hpinkos. |
I know the public API didn't change, but this improves performance for some cases, right? Update CHANGES.md? |
'use strict'; | ||
|
||
var cache = new LRUCache(256, 10000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this was discussed and this is the best solution - if so please point me to it, but this is not how I originally suggested to implement this.
We generally want to centralize all the time-related cache logic so I suggested that we use the render loop to flush any caches just like we do for the shader cache. Are we sure this setInterval
approach is better? I think explicit and consistent control of caches in our render loop (but still based on time stamp) would be more cohesive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was some discussion here: #6284 (comment)
Since sampleTerrain
doesn't have access to frameState
some of the options were:
- Scene edits a global property in
sampleTerrain
that tells it to flush the cache. sampleTerrain
usesrequestAnimationFrame
to hook into the render loop to flush the cache, makingsampleTerrain
self contained.sampleTerrain
usessetInterval
instead ofrequestAnimationFrame
.
One benefit of the third approach (which I guess is opposite your original suggestion) is that sampleTerrain
is not tied to the render loop at all, making it possible to use in Node.js or wherever else the Cesium engine isn't actually running.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I did not think about the standalone use case. I'm still not sure that a standalone API would implicitly cache and flush like this (imagine if an OpenGL driver did thinks like this); the user would likely have more fine-grained control and would be able to pass in a cache policy or whatever.
Still not convinced that having random caches with random set intervals is a good design in Cesium, but proceed as you see fit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still not convinced that having random caches with random set intervals is a good design in Cesium
I think everyone agrees with this sentiment. In my opinion what Cesium really needs is a shared centralized cache (just like we also need a centralized worker pool).
Personally I'm fine with keeping it as it may still be useful for other cases, and the caching system can be changed whenever we are ready. But if anyone wants to revert that sounds fine too. |
LRUCache
, a data structure for managing a ordered list based on what was most recently accessed. It has aDoublyLinkedList
for keeping track of the order, and a dictionary for fast lookups. It has a capacity option so the size doesn't grow beyond the capacity, and an expiration option to clean out old entries whenLRUCache.prune
is calledDoublyLinkedList.addFront
for pushing nodes to the front of the listDoublyLinkedList.moveToFront
for moving an existing node to the front of the listDoublyLinkedList.removeAfter
for removing all nodes including and after a given indexsampleTerrain
so it stores recently fetched tiles in anLRUCache
. It sets the cache size limit to 256 entries, and sets the cache entries to expire and get cleared out after 10 seconds without being accessed.