blob: 582db3638795d50ab9f8e5c2160c2e0fca153377 [file] [edit]
/* Copyright (c) 2026 The Khronos Group Inc.
* Copyright (c) 2026 Valve Corporation
* Copyright (c) 2026 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "sync_access_map.h"
namespace syncval {
void AccessMap::Assign(const AccessMap& other) {
auto temp_copy(other.impl_map_);
impl_map_.swap(temp_copy);
}
AccessMap::iterator AccessMap::LowerBound(ResourceAddress range_begin) {
auto it = impl_map_.lower_bound(AccessRange(range_begin, range_begin));
return it;
}
AccessMap::const_iterator AccessMap::LowerBound(ResourceAddress range_begin) const {
auto it = impl_map_.lower_bound(AccessRange(range_begin, range_begin));
return it;
}
AccessMap::iterator AccessMap::Erase(const iterator& pos) {
assert(pos != end());
return impl_map_.erase(pos);
}
void AccessMap::Erase(iterator first, iterator last) {
auto current = first;
while (current != last) {
assert(current != end());
current = impl_map_.erase(current);
}
}
AccessMap::iterator AccessMap::Merge(iterator first, iterator last) {
assert(first != last);
auto merge_last = last;
--merge_last;
assert(first != merge_last);
const AccessRange merged_range(first->first.begin, merge_last->first.end);
auto node = impl_map_.extract(merge_last);
Erase(first, last);
node.key() = merged_range;
return impl_map_.insert(last, std::move(node));
}
AccessMap::iterator AccessMap::Insert(const_iterator hint, const AccessRange& range, const AccessState& access_state) {
assert(range.non_empty());
bool hint_open;
const_iterator impl_next = hint;
if (impl_map_.empty()) {
hint_open = true;
} else if (impl_next == impl_map_.cbegin()) {
hint_open = range.strictly_less(impl_next->first);
} else if (impl_next == impl_map_.cend()) {
auto impl_prev = impl_next;
--impl_prev;
hint_open = range.strictly_greater(impl_prev->first);
} else {
auto impl_prev = impl_next;
--impl_prev;
hint_open = range.strictly_greater(impl_prev->first) && range.strictly_less(impl_next->first);
}
if (!hint_open) {
// Hint was unhelpful, fall back to the non-hinted version
auto plain_insert = Insert(range, access_state);
return plain_insert.first;
}
auto impl_insert = impl_map_.insert(impl_next, {range, access_state});
return iterator(impl_insert);
}
std::pair<AccessMap::iterator, bool> AccessMap::Insert(const AccessRange& range, const AccessState& access_state) {
assert(range.non_empty());
// Look for range conflicts (and an insertion point, which makes the lower_bound *not* wasted work)
// we don't have to check upper if just check that lower doesn't intersect (which it would if lower != upper)
auto lower = LowerBound(range.begin);
if (lower == end() || !lower->first.intersects(range)) {
// range is not even partially overlapped, and lower is strictly > than key
return {impl_map_.emplace_hint(lower, range, access_state), true};
}
// We don't replace
return {lower, false};
}
AccessMap::iterator AccessMap::InfillGap(const_iterator range_lower_bound, const AccessRange& range,
const AccessState& access_state) {
assert(LowerBound(range.begin) == range_lower_bound);
assert(range_lower_bound == end() || range.strictly_less(range_lower_bound->first));
return impl_map_.insert(range_lower_bound, {range, access_state});
}
void AccessMap::InfillGaps(const AccessRange& range, const AccessState& access_state) {
auto pos = LowerBound(range.begin);
ResourceAddress begin = range.begin;
while (begin < range.end) {
if (pos == end() || begin < pos->first.begin) {
const ResourceAddress gap_end = (pos == end()) ? range.end : std::min(range.end, pos->first.begin);
Insert(pos, {begin, gap_end}, access_state);
begin = gap_end;
} else {
begin = std::min(range.end, pos->first.end);
++pos;
}
}
}
AccessMap::iterator AccessMap::Split(const iterator split_it, const index_type& index) {
const auto range = split_it->first;
if (!range.includes(index)) {
return split_it; // If we don't have a valid split point, just return the iterator
}
AccessRange lower_range(range.begin, index);
if (lower_range.empty()) {
// This is a noop, we're keeping the upper half which is the same as split_it
return split_it;
}
// Save the contents and erase
auto value = split_it->second;
auto next_it = impl_map_.erase(split_it);
AccessRange upper_range(index, range.end);
assert(!upper_range.empty()); // Upper range cannot be empty
// Copy value to the upper range
// NOTE: we insert from upper to lower because that's what emplace_hint can do in constant time
assert(impl_map_.find(upper_range) == impl_map_.end());
next_it = impl_map_.emplace_hint(next_it, std::make_pair(upper_range, value));
// Move value to the lower range (we can move since the upper range already got a copy of value)
assert(impl_map_.find(lower_range) == impl_map_.end());
next_it = impl_map_.emplace_hint(next_it, std::make_pair(lower_range, std::move(value)));
// Iterator to the beginning of the lower range
return next_it;
}
AccessMap::iterator Split(AccessMap::iterator in, AccessMap& map, const AccessRange& range) {
assert(in != map.end()); // Not designed for use with invalid iterators...
const AccessRange in_range = in->first;
const AccessRange split_range = in_range & range;
if (split_range.empty()) {
return map.end();
}
auto pos = in;
if (split_range.begin != in_range.begin) {
pos = map.Split(pos, split_range.begin);
++pos;
}
if (split_range.end != in_range.end) {
pos = map.Split(pos, split_range.end);
}
return pos;
}
void Consolidate(AccessMap& map) {
using It = AccessMap::iterator;
It current = map.begin();
const It map_end = map.end();
// To be included in a merge range there must be no gap in the AccessRange space, and the mapped_type values must match
auto can_merge = [](const It& last, const It& cur) {
return cur->first.begin == last->first.end && cur->second == last->second;
};
while (current != map_end) {
// Establish a trival merge range at the current location, advancing current. Merge range is inclusive of merge_last
const It merge_first = current;
It merge_last = current;
++current;
// Expand the merge range as much as possible
while (current != map_end && can_merge(merge_last, current)) {
merge_last = current;
++current;
}
// Current isn't in the active merge range. If there is a non-trivial merge range, we resolve it here.
if (merge_first != merge_last) {
map.Merge(merge_first, current);
}
}
}
} // namespace syncval