Skip to content

Instantly share code, notes, and snippets.

@Infernio
Created October 10, 2019 01:21
Show Gist options
  • Save Infernio/af3f9f89f9994645516b8ac0f0b659dc to your computer and use it in GitHub Desktop.
Save Infernio/af3f9f89f9994645516b8ac0f0b659dc to your computer and use it in GitHub Desktop.

Basically (for certain definitions of 'basic'), the patching (on PBash, I don't really understand how it works for CBash) works like this:

  1. The user clicks Build Patch
  2. PatchDialog.PatchExecute is called. This is the central method.
  3. It calls init_patchers_data, which calls initData on each patcher.
  4. Now it differs from patcher to patcher. Some use initData to read a lot of mod file to gether information, some don't use it at all (because they don't need it).
  5. Next, PatchExecute calls initFactories. This asks every patcher, 'hey, which record types do you actually need to read and write?'
  6. Every patcher answers, and we make a union of the results.
  7. Next, PatchExecute calls scanLoadMods. This loads every file in the load order, skipping all record types that no patcher wanted. Most patchers stick every record they could potentially patch into the BP at this point (you'll see why later).
  8. This is where the BP merges mods, resolves aliases, applies Filter and IIM tags, etc.
  9. Then, it passes the loaded ModFile instances to the patchers (via scanModFile), and they read the information they're interested in from those files.
  10. Next, PatchExecute calls buildPatch. This calls buildPatch on each patcher. The name is quite misleading, this is where the actual patcher logic happens.
  11. Each patcher uses the information it has gathered in the previous stage to do its importing, merging, tweaking, whatever. They operate on the records that were placed in the BP in step 7 here - meaning that different patcher's changes merge together automatically! That's the reason patchers add every record they could patch to the BP.
  12. The BP would be gigantic if we kept all those records. So the patchers make keepers - whenever they change a record, they grab such a keeper and pass the record's fid (that's Wrye Bash's name for FormIDs) to it.
  13. Finally, PatchExecute calls _save_pbash. This writes out the finished BP, but only the records which were passed to a keeper in stage 12.

I skipped out a whole ton of complexity - long fids vs short fids, mappers, swappers, ImportPatchers vs SpecialPatchers, _SimpleImporters (aka how to define a patcher in 6 lines of code), the interaction with records code, and obviously CBash

The code marks two of these stages with a comment (#try to speed this up!) - 3 and 7. And that's quite correct, those two are by far the slowest. The second one can't easily be sped up, but you might have noticed something about the first one. It happens before we ask each patcher what record types they need. So each patcher needs to load the files they are interested in - and if two patchers want the same file, it will be loaded twice. One of the major goals of issue 312 is to get rid of exactly this - move stage 5 before stage 3. However, this is tough - stage 3 is shared with CBash, so we must be careful not to break CBash. Additionally, many patchers will require rewriting when we do this, so it's a nogo before we've refactored the patchers enough that they can use an ABC, meaning we'd only have to rewrite one implementation.

Next time you build a patch, look how long the Preparing stage takes - that's stage 3 in the list above - and how long the Loading... stage takes - that's stage 7.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment