[core] Immutable/Mutable pattern for Placement
This commit is contained in:
@@ -63,7 +63,7 @@ public:
|
||||
LineAtlas& lineAtlas_,
|
||||
PatternAtlas& patternAtlas_,
|
||||
std::vector<std::reference_wrapper<RenderLayer>> layersNeedPlacement_,
|
||||
std::shared_ptr<const Placement> placement_,
|
||||
Immutable<Placement> placement_,
|
||||
bool updateSymbolOpacities_)
|
||||
: RenderTree(std::move(parameters_)),
|
||||
layerRenderItems(std::move(layerRenderItems_)),
|
||||
@@ -73,7 +73,6 @@ public:
|
||||
layersNeedPlacement(std::move(layersNeedPlacement_)),
|
||||
placement(std::move(placement_)),
|
||||
updateSymbolOpacities(updateSymbolOpacities_) {
|
||||
assert(placement);
|
||||
}
|
||||
|
||||
void prepare() override {
|
||||
@@ -99,7 +98,7 @@ public:
|
||||
std::reference_wrapper<LineAtlas> lineAtlas;
|
||||
std::reference_wrapper<PatternAtlas> patternAtlas;
|
||||
std::vector<std::reference_wrapper<RenderLayer>> layersNeedPlacement;
|
||||
std::shared_ptr<const Placement> placement;
|
||||
Immutable<Placement> placement;
|
||||
bool updateSymbolOpacities;
|
||||
};
|
||||
|
||||
@@ -117,7 +116,6 @@ RenderOrchestrator::RenderOrchestrator(
|
||||
, sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>())
|
||||
, layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>())
|
||||
, renderLight(makeMutable<Light::Impl>())
|
||||
, placement(std::make_unique<Placement>(TransformState{}, MapMode::Static, TransitionOptions{}, true))
|
||||
, backgroundLayerAsColor(backgroundLayerAsColor_) {
|
||||
glyphManager->setObserver(this);
|
||||
imageManager->setObserver(this);
|
||||
@@ -386,16 +384,17 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(const UpdatePar
|
||||
// We want new symbols to show up faster, however simple setting `placementChanged` to `true` would
|
||||
// initiate placement too often as new buckets ususally come from several rendered tiles in a row within
|
||||
// a short period of time. Instead, we squeeze placement update period to coalesce buckets updates from several tiles.
|
||||
if (symbolBucketsAdded) placement->setMaximumUpdatePeriod(Milliseconds(30));
|
||||
renderTreeParameters->placementChanged = !placement->stillRecent(updateParameters.timePoint, updateParameters.transformState.getZoom());
|
||||
optional<Duration> maximumPlacementUpdatePeriod;
|
||||
if (symbolBucketsAdded) maximumPlacementUpdatePeriod = optional<Duration>(Milliseconds(30));
|
||||
renderTreeParameters->placementChanged = !placementController.placementIsRecent(updateParameters.timePoint, updateParameters.transformState.getZoom(), maximumPlacementUpdatePeriod);
|
||||
symbolBucketsChanged |= renderTreeParameters->placementChanged;
|
||||
|
||||
std::set<std::string> usedSymbolLayers;
|
||||
if (renderTreeParameters->placementChanged) {
|
||||
placement = std::make_shared<Placement>(
|
||||
Mutable<Placement> placement = makeMutable<Placement>(
|
||||
updateParameters.transformState, updateParameters.mode,
|
||||
updateParameters.transitionOptions, updateParameters.crossSourceCollisions,
|
||||
std::move(placement));
|
||||
placementController.getPlacement());
|
||||
|
||||
for (auto it = layersNeedPlacement.rbegin(); it != layersNeedPlacement.rend(); ++it) {
|
||||
const RenderLayer& layer = *it;
|
||||
@@ -408,10 +407,11 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(const UpdatePar
|
||||
for (const auto& entry : renderSources) {
|
||||
entry.second->updateFadingTiles();
|
||||
}
|
||||
placementController.setPlacement(std::move(placement));
|
||||
} else {
|
||||
placement->setStale();
|
||||
placementController.setPlacementStale();
|
||||
}
|
||||
renderTreeParameters->symbolFadeChange = placement->symbolFadeChange(updateParameters.timePoint);
|
||||
renderTreeParameters->symbolFadeChange = placementController.getPlacement()->symbolFadeChange(updateParameters.timePoint);
|
||||
}
|
||||
|
||||
renderTreeParameters->needsRepaint = isMapModeContinuous && hasTransitions(updateParameters.timePoint);
|
||||
@@ -435,7 +435,7 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(const UpdatePar
|
||||
*lineAtlas,
|
||||
*patternAtlas,
|
||||
std::move(layersNeedPlacement),
|
||||
placement,
|
||||
placementController.getPlacement(),
|
||||
symbolBucketsChanged);
|
||||
}
|
||||
|
||||
@@ -473,11 +473,11 @@ void RenderOrchestrator::queryRenderedSymbols(std::unordered_map<std::string, st
|
||||
if (crossTileSymbolIndexLayers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto renderedSymbols = placement->getCollisionIndex().queryRenderedSymbols(geometry);
|
||||
const Placement& placement = *placementController.getPlacement();
|
||||
auto renderedSymbols = placement.getCollisionIndex().queryRenderedSymbols(geometry);
|
||||
std::vector<std::reference_wrapper<const RetainedQueryData>> bucketQueryData;
|
||||
for (auto entry : renderedSymbols) {
|
||||
bucketQueryData.emplace_back(placement->getQueryData(entry.first));
|
||||
bucketQueryData.emplace_back(placement.getQueryData(entry.first));
|
||||
}
|
||||
// Although symbol query is global, symbol results are only sortable within a bucket
|
||||
// For a predictable global sort renderItems, we sort the buckets based on their corresponding tile position
|
||||
@@ -643,7 +643,7 @@ bool RenderOrchestrator::hasTransitions(TimePoint timePoint) const {
|
||||
}
|
||||
}
|
||||
|
||||
if (placement->hasTransitions(timePoint)) {
|
||||
if (placementController.hasTransitions(timePoint)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ private:
|
||||
RenderLight renderLight;
|
||||
|
||||
CrossTileSymbolIndex crossTileSymbolIndex;
|
||||
std::shared_ptr<Placement> placement;
|
||||
PlacementController placementController;
|
||||
|
||||
const bool backgroundLayerAsColor;
|
||||
bool contextLost = false;
|
||||
|
||||
@@ -58,16 +58,49 @@ const CollisionGroups::CollisionGroup& CollisionGroups::get(const std::string& s
|
||||
}
|
||||
}
|
||||
|
||||
Placement::Placement(const TransformState& state_, MapMode mapMode_, style::TransitionOptions transitionOptions_, const bool crossSourceCollisions, std::shared_ptr<const Placement> prevPlacement_)
|
||||
// PlacementController implemenation
|
||||
|
||||
PlacementController::PlacementController()
|
||||
: placement(makeMutable<Placement>(TransformState{}, MapMode::Static, style::TransitionOptions{}, true, nullopt)) {
|
||||
}
|
||||
|
||||
void PlacementController::setPlacement(Immutable<Placement> placement_) {
|
||||
placement = std::move(placement_);
|
||||
stale = false;
|
||||
}
|
||||
|
||||
bool PlacementController::placementIsRecent(TimePoint now, const float zoom, optional<Duration> maximumDuration) const {
|
||||
if (!placement->transitionsEnabled()) return false;
|
||||
|
||||
auto updatePeriod = placement->getUpdatePeriod(zoom);
|
||||
|
||||
if (maximumDuration) {
|
||||
updatePeriod = std::min(*maximumDuration, updatePeriod);
|
||||
}
|
||||
|
||||
return placement->getCommitTime() + updatePeriod > now;
|
||||
}
|
||||
|
||||
|
||||
bool PlacementController::hasTransitions(TimePoint now) const {
|
||||
if (!placement->transitionsEnabled()) return false;
|
||||
|
||||
if (stale) return true;
|
||||
|
||||
return placement->hasTransitions(now);
|
||||
}
|
||||
|
||||
// Placement implementation
|
||||
|
||||
Placement::Placement(const TransformState& state_, MapMode mapMode_, style::TransitionOptions transitionOptions_, const bool crossSourceCollisions, optional<Immutable<Placement>> prevPlacement_)
|
||||
: collisionIndex(state_)
|
||||
, mapMode(mapMode_)
|
||||
, transitionOptions(std::move(transitionOptions_))
|
||||
, placementZoom(state_.getZoom())
|
||||
, collisionGroups(crossSourceCollisions)
|
||||
, prevPlacement(std::move(prevPlacement_))
|
||||
{
|
||||
, prevPlacement(std::move(prevPlacement_)) {
|
||||
if (prevPlacement) {
|
||||
prevPlacement->prevPlacement.reset(); // Only hold on to one placement back
|
||||
prevPlacement->get()->prevPlacement = nullopt; // Only hold on to one placement back
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,9 +221,9 @@ void Placement::placeBucket(
|
||||
const CollisionFeature& textCollisionFeature = symbolInstance.textCollisionFeature;
|
||||
|
||||
const auto updatePreviousOrientationIfNotPlaced = [&](bool isPlaced) {
|
||||
if (bucket.allowVerticalPlacement && !isPlaced && prevPlacement) {
|
||||
auto prevOrientation = prevPlacement->placedOrientations.find(symbolInstance.crossTileID);
|
||||
if (prevOrientation != prevPlacement->placedOrientations.end()) {
|
||||
if (bucket.allowVerticalPlacement && !isPlaced && getPrevPlacement()) {
|
||||
auto prevOrientation = getPrevPlacement()->placedOrientations.find(symbolInstance.crossTileID);
|
||||
if (prevOrientation != getPrevPlacement()->placedOrientations.end()) {
|
||||
placedOrientations[symbolInstance.crossTileID] = prevOrientation->second;
|
||||
}
|
||||
}
|
||||
@@ -251,9 +284,9 @@ void Placement::placeBucket(
|
||||
// If this symbol was in the last placement, shift the previously used
|
||||
// anchor to the front of the anchor list, only if the previous anchor
|
||||
// is still in the anchor list.
|
||||
if (prevPlacement) {
|
||||
auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
|
||||
if (prevOffset != prevPlacement->variableOffsets.end()) {
|
||||
if (getPrevPlacement()) {
|
||||
auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
|
||||
if (prevOffset != getPrevPlacement()->variableOffsets.end()) {
|
||||
const auto prevAnchor = prevOffset->second.anchor;
|
||||
auto found = std::find(variableTextAnchors.begin(), variableTextAnchors.end(), prevAnchor);
|
||||
if (found != variableTextAnchors.begin() && found != variableTextAnchors.end()) {
|
||||
@@ -300,11 +333,11 @@ void Placement::placeBucket(
|
||||
|
||||
// If this label was placed in the previous placement, record the anchor position
|
||||
// to allow us to animate the transition
|
||||
if (prevPlacement) {
|
||||
auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
|
||||
auto prevPlacements = prevPlacement->placements.find(symbolInstance.crossTileID);
|
||||
if (prevOffset != prevPlacement->variableOffsets.end() &&
|
||||
prevPlacements != prevPlacement->placements.end() &&
|
||||
if (getPrevPlacement()) {
|
||||
auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
|
||||
auto prevPlacements = getPrevPlacement()->placements.find(symbolInstance.crossTileID);
|
||||
if (prevOffset != getPrevPlacement()->variableOffsets.end() &&
|
||||
prevPlacements != getPrevPlacement()->placements.end() &&
|
||||
prevPlacements->second.text) {
|
||||
// TODO: The prevAnchor seems to be unused, needs to be fixed.
|
||||
prevAnchor = prevOffset->second.anchor;
|
||||
@@ -350,9 +383,9 @@ void Placement::placeBucket(
|
||||
|
||||
// If we didn't get placed, we still need to copy our position from the last placement for
|
||||
// fade animations
|
||||
if (!placeText && prevPlacement) {
|
||||
auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
|
||||
if (prevOffset != prevPlacement->variableOffsets.end()) {
|
||||
if (!placeText && getPrevPlacement()) {
|
||||
auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
|
||||
if (prevOffset != getPrevPlacement()->variableOffsets.end()) {
|
||||
variableOffsets[symbolInstance.crossTileID] = prevOffset->second;
|
||||
}
|
||||
}
|
||||
@@ -459,19 +492,18 @@ void Placement::placeBucket(
|
||||
}
|
||||
|
||||
void Placement::commit(TimePoint now, const double zoom) {
|
||||
assert(prevPlacement);
|
||||
commitTime = now;
|
||||
|
||||
bool placementChanged = false;
|
||||
|
||||
prevZoomAdjustment = prevPlacement->zoomAdjustment(zoom);
|
||||
prevZoomAdjustment = getPrevPlacement()->zoomAdjustment(zoom);
|
||||
|
||||
float increment = prevPlacement->symbolFadeChange(commitTime);
|
||||
float increment = getPrevPlacement()->symbolFadeChange(commitTime);
|
||||
|
||||
// add the opacities from the current placement, and copy their current values from the previous placement
|
||||
for (auto& jointPlacement : placements) {
|
||||
auto prevOpacity = prevPlacement->opacities.find(jointPlacement.first);
|
||||
if (prevOpacity != prevPlacement->opacities.end()) {
|
||||
auto prevOpacity = getPrevPlacement()->opacities.find(jointPlacement.first);
|
||||
if (prevOpacity != getPrevPlacement()->opacities.end()) {
|
||||
opacities.emplace(jointPlacement.first, JointOpacityState(prevOpacity->second, increment, jointPlacement.second.text, jointPlacement.second.icon));
|
||||
placementChanged = placementChanged ||
|
||||
jointPlacement.second.icon != prevOpacity->second.icon.placed ||
|
||||
@@ -483,7 +515,7 @@ void Placement::commit(TimePoint now, const double zoom) {
|
||||
}
|
||||
|
||||
// copy and update values from the previous placement that aren't in the current placement but haven't finished fading
|
||||
for (auto& prevOpacity : prevPlacement->opacities) {
|
||||
for (auto& prevOpacity : getPrevPlacement()->opacities) {
|
||||
if (opacities.find(prevOpacity.first) == opacities.end()) {
|
||||
JointOpacityState jointOpacity(prevOpacity.second, increment, false, false);
|
||||
if (!jointOpacity.isHidden()) {
|
||||
@@ -493,7 +525,7 @@ void Placement::commit(TimePoint now, const double zoom) {
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& prevOffset : prevPlacement->variableOffsets) {
|
||||
for (auto& prevOffset : getPrevPlacement()->variableOffsets) {
|
||||
const uint32_t crossTileID = prevOffset.first;
|
||||
auto foundOffset = variableOffsets.find(crossTileID);
|
||||
auto foundOpacity = opacities.find(crossTileID);
|
||||
@@ -502,7 +534,7 @@ void Placement::commit(TimePoint now, const double zoom) {
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& prevOrientation : prevPlacement->placedOrientations) {
|
||||
for (auto& prevOrientation : getPrevPlacement()->placedOrientations) {
|
||||
const uint32_t crossTileID = prevOrientation.first;
|
||||
auto foundOrientation = placedOrientations.find(crossTileID);
|
||||
auto foundOpacity = opacities.find(crossTileID);
|
||||
@@ -511,7 +543,7 @@ void Placement::commit(TimePoint now, const double zoom) {
|
||||
}
|
||||
}
|
||||
|
||||
fadeStartTime = placementChanged ? commitTime : prevPlacement->fadeStartTime;
|
||||
fadeStartTime = placementChanged ? commitTime : getPrevPlacement()->fadeStartTime;
|
||||
}
|
||||
|
||||
void Placement::updateLayerBuckets(const RenderLayer& layer, const TransformState& state, bool updateOpacities) const {
|
||||
@@ -948,12 +980,11 @@ void Placement::markUsedOrientation(SymbolBucket& bucket, style::TextWritingMode
|
||||
}
|
||||
|
||||
float Placement::symbolFadeChange(TimePoint now) const {
|
||||
if (mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions &&
|
||||
if (transitionsEnabled() &&
|
||||
transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0)) {
|
||||
return std::chrono::duration<float>(now - commitTime) / transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) + prevZoomAdjustment;
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float Placement::zoomAdjustment(const float zoom) const {
|
||||
@@ -968,33 +999,16 @@ Duration Placement::getUpdatePeriod(const float zoom) const {
|
||||
// Even if transitionOptions.duration is set to a value < 300ms, we still wait for this default transition duration
|
||||
// before attempting another placement operation.
|
||||
const auto fadeDuration = std::max(util::DEFAULT_TRANSITION_DURATION, transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION));
|
||||
const auto adjustedDuration = std::chrono::duration_cast<Duration>(fadeDuration * (1.0 - zoomAdjustment(zoom)));
|
||||
if (maximumUpdatePeriod) {
|
||||
return std::min(*maximumUpdatePeriod, adjustedDuration);
|
||||
}
|
||||
return adjustedDuration;
|
||||
return std::chrono::duration_cast<Duration>(fadeDuration * (1.0 - zoomAdjustment(zoom)));
|
||||
}
|
||||
|
||||
bool Placement::transitionsEnabled() const {
|
||||
return mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions;
|
||||
}
|
||||
|
||||
bool Placement::hasTransitions(TimePoint now) const {
|
||||
if (mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions) {
|
||||
return stale || std::chrono::duration<float>(now - fadeStartTime) < transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Placement::stillRecent(TimePoint now, const float zoom) const {
|
||||
return mapMode == MapMode::Continuous &&
|
||||
transitionOptions.enablePlacementTransitions &&
|
||||
commitTime + getUpdatePeriod(zoom) > now;
|
||||
}
|
||||
|
||||
void Placement::setMaximumUpdatePeriod(Duration duration) {
|
||||
maximumUpdatePeriod = duration;
|
||||
}
|
||||
|
||||
void Placement::setStale() {
|
||||
stale = true;
|
||||
assert(transitionsEnabled());
|
||||
return std::chrono::duration<float>(now - fadeStartTime) < transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION);
|
||||
}
|
||||
|
||||
const CollisionIndex& Placement::getCollisionIndex() const {
|
||||
|
||||
@@ -97,22 +97,39 @@ public:
|
||||
std::shared_ptr<FeatureIndex> featureIndex;
|
||||
bool showCollisionBoxes;
|
||||
};
|
||||
|
||||
class Placement;
|
||||
|
||||
class PlacementController {
|
||||
public:
|
||||
PlacementController();
|
||||
void setPlacement(Immutable<Placement>);
|
||||
const Immutable<Placement>& getPlacement() const { return placement; }
|
||||
void setPlacementStale() { stale = true; }
|
||||
bool placementIsRecent(TimePoint now, const float zoom, optional<Duration> maximumDuration = nullopt) const;
|
||||
bool hasTransitions(TimePoint now) const;
|
||||
|
||||
private:
|
||||
Immutable<Placement> placement;
|
||||
bool stale = false;
|
||||
};
|
||||
|
||||
class Placement {
|
||||
public:
|
||||
Placement(const TransformState&, MapMode, style::TransitionOptions, const bool crossSourceCollisions, std::shared_ptr<const Placement> prevPlacement = nullptr);
|
||||
Placement(const TransformState&, MapMode, style::TransitionOptions, const bool crossSourceCollisions, optional<Immutable<Placement>> prevPlacement);
|
||||
void placeLayer(const RenderLayer&, const mat4&, bool showCollisionBoxes);
|
||||
void commit(TimePoint, const double zoom);
|
||||
void updateLayerBuckets(const RenderLayer&, const TransformState&, bool updateOpacities) const;
|
||||
float symbolFadeChange(TimePoint now) const;
|
||||
bool hasTransitions(TimePoint now) const;
|
||||
bool transitionsEnabled() const;
|
||||
|
||||
const CollisionIndex& getCollisionIndex() const;
|
||||
TimePoint getCommitTime() const { return commitTime; }
|
||||
Duration getUpdatePeriod(const float zoom) const;
|
||||
|
||||
float zoomAdjustment(const float zoom) const;
|
||||
|
||||
bool stillRecent(TimePoint now, const float zoom) const;
|
||||
void setMaximumUpdatePeriod(Duration);
|
||||
void setStale();
|
||||
|
||||
const RetainedQueryData& getQueryData(uint32_t bucketInstanceId) const;
|
||||
private:
|
||||
friend SymbolBucket;
|
||||
@@ -125,8 +142,9 @@ private:
|
||||
void updateBucketOpacities(SymbolBucket&, const TransformState&, std::set<uint32_t>&) const;
|
||||
void markUsedJustification(SymbolBucket&, style::TextVariableAnchorType, const SymbolInstance&, style::TextWritingModeType orientation) const;
|
||||
void markUsedOrientation(SymbolBucket&, style::TextWritingModeType, const SymbolInstance&) const;
|
||||
float zoomAdjustment(const float zoom) const;
|
||||
Duration getUpdatePeriod(const float zoom) const;
|
||||
const Placement* getPrevPlacement() const {
|
||||
return prevPlacement ? prevPlacement->get() : nullptr;
|
||||
}
|
||||
|
||||
CollisionIndex collisionIndex;
|
||||
|
||||
@@ -143,12 +161,9 @@ private:
|
||||
std::unordered_map<uint32_t, VariableOffset> variableOffsets;
|
||||
std::unordered_map<uint32_t, style::TextWritingModeType> placedOrientations;
|
||||
|
||||
bool stale = false;
|
||||
|
||||
std::unordered_map<uint32_t, RetainedQueryData> retainedQueryData;
|
||||
CollisionGroups collisionGroups;
|
||||
mutable std::shared_ptr<const Placement> prevPlacement;
|
||||
optional<Duration> maximumUpdatePeriod;
|
||||
mutable optional<Immutable<Placement>> prevPlacement;
|
||||
|
||||
// Used for debug purposes.
|
||||
std::unordered_map<const CollisionFeature*, std::vector<ProjectedCollisionBox>> collisionCircles;
|
||||
|
||||
Reference in New Issue
Block a user