Sort queue of blocks to visit by blend file and on-disk order

This gives a small speedup to dependency tracing.
This commit is contained in:
Sybren A. Stüvel 2018-03-07 17:14:35 +01:00
parent 09a0866c14
commit cc20b0bfd5
2 changed files with 37 additions and 6 deletions

View File

@ -121,7 +121,7 @@ class BlendFile:
fileobj.close()
raise exceptions.BlendFileError("File is not a blend file", path)
self.blocks = []
self.blocks = [] # BlendFileBlocks, in disk order.
self.code_index = collections.defaultdict(list)
self.structs = []
self.sdna_index_from_id = {}
@ -333,6 +333,7 @@ class BlendFile:
raise exceptions.SegmentationFault('address does not exist', address) from None
@functools.total_ordering
class BlendFileBlock:
"""
Instance of a struct.
@ -405,6 +406,14 @@ class BlendFileBlock:
self.addr_old == other.addr_old and
self.bfile.filepath == other.bfile.filepath)
def __lt__(self, other: 'BlendFileBlock') -> bool:
"""Order blocks by file path and offset within that file."""
if not isinstance(other, BlendFileBlock):
raise NotImplemented()
my_key = self.bfile.filepath, self.file_offset
other_key = other.bfile.filepath, other.file_offset
return my_key < other_key
def __bool__(self) -> bool:
"""Data blocks are always True."""
return True

View File

@ -35,17 +35,21 @@ class _BlockIterator:
limit_to: typing.Set[blendfile.BlendFileBlock] = frozenset(),
) -> typing.Iterator[blendfile.BlendFileBlock]:
"""Expand blocks with dependencies from other libraries."""
if limit_to:
self._queue_named_blocks(bfile, limit_to)
else:
self._queue_all_blocks(bfile)
blocks_per_lib = yield from self._visit_blocks(bfile, limit_to)
yield from self._visit_linked_blocks(blocks_per_lib)
def _visit_blocks(self, bfile, limit_to):
bpath = bfile.filepath.absolute().resolve()
root_dir = bpathlib.BlendPath(bpath.parent)
# Mapping from library path to data blocks to expand.
blocks_per_lib = collections.defaultdict(set)
if limit_to:
self._queue_named_blocks(bfile, limit_to)
else:
self._queue_all_blocks(bfile)
while self.to_visit:
block = self.to_visit.popleft()
assert isinstance(block, blendfile.BlendFileBlock)
@ -75,6 +79,9 @@ class _BlockIterator:
self.blocks_yielded.add((bpath, block.addr_old))
yield block
return blocks_per_lib
def _visit_linked_blocks(self, blocks_per_lib):
# We've gone through all the blocks in this file, now open the libraries
# and iterate over the blocks referred there.
for lib_bpath, idblocks in blocks_per_lib.items():
@ -95,6 +102,7 @@ class _BlockIterator:
# to do with them anyway.
self.to_visit.extend(block for block in bfile.blocks
if block.code != b'DATA')
self._sort_queue()
def _queue_named_blocks(self,
bfile: blendfile.BlendFile,
@ -106,6 +114,7 @@ class _BlockIterator:
The queued blocks are loaded from the actual blend file, and
selected by name.
"""
for to_find in limit_to:
assert to_find.code == b'ID'
name_to_find = to_find[b'name']
@ -116,9 +125,22 @@ class _BlockIterator:
if block.id_name == name_to_find:
log.debug('Queueing %r from file %s', block, bfile.filepath)
self.to_visit.append(block)
self._sort_queue()
def _queue_dependencies(self, block: blendfile.BlendFileBlock):
self.to_visit.extend(expanders.expand_block(block))
self._sort_queue()
def _sort_queue(self):
"""Sort the queued blocks by file and by offset.
This allows us to go through the blend files sequentially.
"""
def sort_key(block: blendfile.BlendFileBlock):
return block.bfile.filepath, block.file_offset
self.to_visit = collections.deque(sorted(self.to_visit, key=sort_key))
def iter_blocks(bfile: blendfile.BlendFile) -> typing.Iterator[blendfile.BlendFileBlock]: