File Map

An id-keyed container for the TFile records of one archive.

Values is kept sorted by id and HashSlots is an open-addressed table pointing into it, so lookups are constant time while iteration still comes out in id order.


MapHash

function MapHash(key, size: Integer): Integer; static;
function MapHashSize(expectedCount: Integer): Integer; static;

MapHash is the slot a key hashes to in a table of size slots, and MapHashSize is the size to give a table holding roughly expectedCount entries.

Ids are already well spread, so hashing one is only a fold back into range. Table sizes are always powers of two - at least twice the expected count, so probing stays short - which makes that fold a mask rather than a modulo, and lands a negative id in range without a correction. Doubling a power of two leaves a power of two, so TFileMap._Grow keeps the property.


TFileMap._Init

procedure TFileMap._Init(expectedCount: Integer);

Empties the map and sizes its hash table for roughly expectedCount entries.

The table is deliberately over-allocated - see MapHashSize - so that probing stays short; TFileMap._Grow doubles it again if it ever fills past 60%.


TFileMap._FindSlot

function TFileMap._FindSlot(key: Integer): Integer;

The hash slot belonging to key: either the one already holding it, or the first free one after where it hashes to. Collisions walk forward one slot at a time and wrap around.


TFileMap._Grow

procedure TFileMap._Grow();

Resizes the hash table to fit one more entry and reinserts every key into it. Entry order is untouched - only the slots that point at the entries are rebuilt.

It is also how a map that TFileMap.Build left without a table gets one.


FileMap.Build

procedure TFileMap.Build(const keys: TIntegerArray; count: Integer);

Fills the map in one pass with an empty TFile per key, sizing the key, value and hash arrays exactly once instead of regrowing them on every insert.

Only the first count entries of keys are used, so callers filling many maps in a row can reuse one buffer. They must be in ascending order, which is how reference tables store them.

Most archives of the cache hold a single file, so a map of one entry - or of none - is left without a hash table at all, HashSize staying 0: the lookups compare against the one key instead of probing, and opening a cache does one allocation less per archive for the hundred thousand odd maps it builds. TFileMap._Grow gives such a map a table if something is later added to it.


TFileMap.Exists

function TFileMap.Exists(key: Integer): Boolean;

Whether the map holds an entry with this id.


TFileMap.IndexOf

function TFileMap.IndexOf(key: Integer): Integer;

The position of this id in Values, or -1 if it is not in the map.


TFileMap.Value

property TFileMap.Value(key: Integer; const val: TFile);
property TFileMap.Value(key: Integer): TFile;

Reads or writes the file stored under key. Reading an id the map does not hold raises.

Writing keeps Values sorted by id. Appending an id above every existing one - which is how reference tables arrive - only costs the append; inserting one in the middle has to shift the entries after it and rebuild the hash table, so prefer TFileMap.Build when filling a map from scratch.


TFileMap.Clear

procedure TFileMap.Clear();

Drops every entry and the hash table with them, leaving the map as if it had never been used.