Skip to content

Create plugin for clearing of combat, sparring, and hunting reports with configurable filtering and overlay UI#5704

Open
pajawojciech wants to merge 3 commits intoDFHack:developfrom
pajawojciech:log-cleaner
Open

Create plugin for clearing of combat, sparring, and hunting reports with configurable filtering and overlay UI#5704
pajawojciech wants to merge 3 commits intoDFHack:developfrom
pajawojciech:log-cleaner

Conversation

@pajawojciech
Copy link
Contributor

@pajawojciech pajawojciech commented Jan 10, 2026

Summary

Adds a new plugin to automatically clear combat/sparring/hunting reports from buffer. Features configurable filtering by type, overlay UI settings, and control panel integration.

This does NOT hide the left-side notifications on screen:
obraz

Features

  • Time-triggered clearing: Runs every 100 ticks
  • Configurable filtering: Separate toggles for combat, sparring, and hunting logs
  • Sparring enabled by default: Targets the most common spam source
  • Command interface: Configure via console commands; auto-enables when filters specified
  • Overlay UI: Settings panel with enable toggle and filter toggles
obraz
  • Control panel integration: Plugin in Gameplay tab with autostart options
obraz

Usage

logcleaner              - show status
logcleaner enable       - enable plugin
logcleaner disable      - disable plugin
logcleaner sparring     - enable sparring filter (auto-enables plugin)
logcleaner all          - enable all filters (auto-enables plugin)
logcleaner combat,hunting - enable specific filters
gui/logcleaner          - open settings overlay

Tests done

  • 50 dwarves training generate ~100-200 reports per 100 ticks
  • Cleaning time < 2 ms (minimal performance impact)
  • Plugin state persists across save/load
  • Autostart works for new embark
  • Old fort ran over 1 year, and all wanted reports preserved (my calculations show in this case ~3 years before buffer overflow)

Three tabs are empty

obraz

Now you can finally keep your reports clean enough to actually spot when Urist McSad is depressed or when a tantrum spiral starts

obraz

Related issues

#3694
#3397

Related PR in scripts repo (gui and autostart)

DFHack/scripts#1536

@pajawojciech pajawojciech marked this pull request as draft January 10, 2026 12:21
@pajawojciech pajawojciech marked this pull request as ready for review January 10, 2026 14:11
@Bumber64
Copy link
Contributor

Bumber64 commented Jan 11, 2026

The frequency should be a prime number (e.g., 97) for timestream reasons. This should be defined as a variable near the top, not a magic number in an if statement.

You need something like this during report deletion:

if (report->flags.bits.announcement)
    erase_from_vector(world->status.announcements, &df::report::id, report_id);

(Popups don't need to be handled because they're deleted upon viewing, even if someone was crazy enough to enable them for combat messages.)

Myk has a similar unfinished plugin. He ran into some kind of issue, but I don't remember specifics. Maybe it was the performance impact of unit.reports.log, which is less an issue at the frequency you're running?

You could probably delete the unit report logs as you're iterating them (by doing so in reverse order). I think DF itself leaves these stale entries lying around, which would be good to clean up so we can iterate less.

@pajawojciech
Copy link
Contributor Author

The frequency should be a prime number

Fixed


You need something like this during report deletion:

world->status.announcements does not contain sparring, combat and hunting reports


Myk has a similar unfinished plugin. He ran into some kind of issue, but I don't remember specifics. Maybe it was the performance impact of unit.reports.log, which is less an issue at the frequency you're running?

I set frequency to 1 (every tick) and still can't see any performance impact. Performance check says that one run takes less than 2 ms.
Myk proposition uses buckets and reinserts reports. I think my version is simpler: foreach unit, get sparring reports (or combat and hunting reports if enabled) and remove them from units and from world.status.reports


You could probably delete the unit report logs as you're iterating them

I accomplish this using log.clear();

@ab9rf ab9rf added this to 53.10-r2 Jan 15, 2026
@ab9rf ab9rf moved this to Being worked on in 53.10-r2 Jan 15, 2026
@ab9rf
Copy link
Member

ab9rf commented Feb 9, 2026

The frequency should be a prime number (e.g., 97) for timestream reasons. This should be defined as a variable near the top, not a magic number in an if statement.

While I agree that the magic number should be defined as a symbolic constant, using a prime number doesn't really have anything directly to do with timestream. The idea behind using prime numbers is to avoid phasing with other tools that also run at periodic tick intervals so that two "expensive" tools don't both always, or frequently, run on the same tick, which might lead to visible stuttering for the user.

We've talked about providing a system where tools that need to run periodically request an "approximate frequency" and Core then alllocates them in a uniform manner across all tools so that everyone gets about the frequency they want while avoiding phasing, but this hasn't been implemented yet.

As to 2 ms per tick: this may not seem by much, but please remember that at 100 fps the total per tick budget is only 10 milliseconds and the target time budget for all of DFHack is 5% of that, or 0.5 ms. If you're using 2 ms per tick, that's 4x over budget and thus not remotely acceptable.

Finally, running more often than once per ten ticks is almost certainly overkill. A lot of things in DF happen once every ten, once every 50, or once every 100 ticks. Given this, 97 is probably an especially good number to use, as it falls just below the "once every 100" frequency.

if (report_ids_to_remove.empty())
return;

// Remove collected reports from global buffers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inefficient, requiring repeated scans of the reports vector for each removal and may have to move elements repeatedly. Consider using std::erase_if instead, which will only make one scan of the vector, and intelligently collapses the gaps.

static bool clear_hunting = false;

static const int32_t CLEANUP_TICK_INTERVAL = 97;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should all be constexpr

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

Labels

None yet

Projects

Status: Being worked on

Development

Successfully merging this pull request may close these issues.

3 participants