blob: ed0b6b5d7beb2de04afb6cd89211cc3d1628e11d [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/scenic/lib/flatland/transform_graph.h"
#include <lib/syslog/cpp/macros.h>
namespace flatland {
TransformGraph::TransformGraph() : TransformGraph(0) {}
TransformGraph::TransformGraph(TransformHandle::InstanceId instance_id)
: instance_id_(instance_id) {}
TransformHandle TransformGraph::CreateTransform() {
FX_DCHECK(is_valid_);
TransformHandle retval(instance_id_, next_transform_id_++);
FX_DCHECK(!working_set_.count(retval));
working_set_.insert(retval);
live_set_.insert(retval);
return retval;
}
bool TransformGraph::ReleaseTransform(TransformHandle handle) {
FX_DCHECK(is_valid_);
auto iter = working_set_.find(handle);
if (iter == working_set_.end()) {
return false;
}
working_set_.erase(iter);
return true;
}
bool TransformGraph::AddChild(TransformHandle parent, TransformHandle child) {
FX_DCHECK(is_valid_);
FX_DCHECK(working_set_.count(parent));
auto [iter, end_iter] = children_.equal_range({parent, NORMAL});
for (; iter != end_iter; ++iter) {
if (iter->second == child) {
return false;
}
}
children_.insert({{parent, NORMAL}, child});
return true;
}
bool TransformGraph::RemoveChild(TransformHandle parent, TransformHandle child) {
FX_DCHECK(is_valid_);
FX_DCHECK(working_set_.count(parent));
auto [iter, end_iter] = children_.equal_range({parent, NORMAL});
for (; iter != end_iter; ++iter) {
if (iter->second == child) {
children_.erase(iter);
return true;
}
}
return false;
}
void TransformGraph::ClearChildren(TransformHandle parent) {
FX_DCHECK(is_valid_);
FX_DCHECK(working_set_.count(parent));
children_.erase({parent, NORMAL});
}
void TransformGraph::SetPriorityChild(TransformHandle parent, TransformHandle child) {
FX_DCHECK(is_valid_);
FX_DCHECK(working_set_.count(parent));
children_.erase({parent, PRIORITY});
children_.insert({{parent, PRIORITY}, child});
}
void TransformGraph::ClearPriorityChild(TransformHandle parent) {
FX_DCHECK(is_valid_);
FX_DCHECK(working_set_.count(parent));
children_.erase({parent, PRIORITY});
}
void TransformGraph::ResetGraph(TransformHandle exception) {
FX_DCHECK(working_set_.count(exception));
working_set_.clear();
working_set_.insert(exception);
children_.clear();
is_valid_ = true;
}
TransformGraph::TopologyData TransformGraph::ComputeAndCleanup(TransformHandle start,
uint64_t max_iterations) {
FX_DCHECK(is_valid_);
FX_DCHECK(working_set_.count(start));
TopologyData data;
// Swap all the live nodes into the dead set, so we can pull them out as we visit them.
std::swap(live_set_, data.dead_transforms);
// Clone our children map. We will remove child links after we visit them, to avoid duplicate
// work when traversing the entire working set of transforms.
PriorityChildMap children_copy = children_;
// Compute the topological set starting from the start transform.
data.sorted_transforms =
Traverse(start, children_copy, &data.cyclical_edges, max_iterations - data.iterations);
data.iterations += data.sorted_transforms.size();
for (auto [transform, child_count] : data.sorted_transforms) {
auto [start, end] = EqualRangeAllPriorities(children_copy, transform);
if (start != children_copy.cend()) {
children_copy.erase(start, end);
}
data.dead_transforms.erase(transform);
live_set_.insert(transform);
}
// Compute the topological set starting from every working set transform, for cleanup purposes.
for (auto transform : working_set_) {
auto working_transforms =
Traverse(transform, children_copy, &data.cyclical_edges, max_iterations - data.iterations);
data.iterations += working_transforms.size();
for (auto [transform, child_count] : working_transforms) {
auto [start, end] = EqualRangeAllPriorities(children_copy, transform);
if (start != children_copy.cend()) {
children_copy.erase(start, end);
}
data.dead_transforms.erase(transform);
live_set_.insert(transform);
}
}
// Cleanup child state for all dead nodes.
for (auto transform : data.dead_transforms) {
auto [start, end] = EqualRangeAllPriorities(children_, transform);
if (start != children_.cend()) {
children_.erase(start, end);
}
}
if (data.iterations >= max_iterations) {
is_valid_ = false;
}
return data;
}
TransformGraph::IteratorPair TransformGraph::EqualRangeAllPriorities(const PriorityChildMap& map,
TransformHandle handle) {
auto start = map.lower_bound({handle, PRIORITY});
auto end = map.upper_bound({handle, NORMAL});
FX_DCHECK(std::distance(start, end) >= 0);
return {start, end};
}
TransformGraph::TopologyVector TransformGraph::Traverse(TransformHandle start,
const PriorityChildMap& children,
ChildMap* cycles, uint64_t max_length) {
TopologyVector retval;
std::vector<IteratorPair> iterator_stack;
std::vector<TransformHandle> ancestors;
std::vector<uint64_t> parent_indices;
// Add the starting handle to the output, and initialize our state.
auto start_pair = EqualRangeAllPriorities(children, start);
iterator_stack.push_back(start_pair);
retval.push_back(
{start, static_cast<uint64_t>(std::distance(start_pair.first, start_pair.second))});
ancestors.push_back(start);
parent_indices.push_back(0);
// Iterate until we're done, or until we run out of space
while (!iterator_stack.empty() && retval.size() < max_length) {
auto& [child_iter, end_iter] = iterator_stack.back();
// If we're at the end of this iterator, pop to the parent iterator.
if (child_iter == end_iter) {
iterator_stack.pop_back();
ancestors.pop_back();
if (!parent_indices.empty()) {
parent_indices.pop_back();
}
continue;
}
const TransformHandle child = child_iter->second;
// We increment the child iterator here, instead of at the end of the loop, because the new
// child may cause the iterator_stack to mutate, invalidating the live references we've
// captured.
++child_iter;
// Search from the bottom of the stack (since it's more likely), looking for a cycle.
if (std::find(ancestors.crbegin(), ancestors.crend(), child) != ancestors.crend()) {
FX_DCHECK(cycles);
FX_DCHECK(!parent_indices.empty());
cycles->insert({retval[parent_indices.back()].handle, child});
} else {
// If the child is not part of a cycle, add it to the sorted list and update our state.
parent_indices.push_back(retval.size());
auto pair = EqualRangeAllPriorities(children, child);
iterator_stack.push_back(pair);
retval.push_back({child, static_cast<uint64_t>(std::distance(pair.first, pair.second))});
ancestors.push_back(child);
}
}
return retval;
}
} // namespace flatland