That’s not how I’d use events or flock managers. A flock manager could be an async process that only runs every second or so that uses rules to create lists of boids that have to consider other boids. That way you potentially reduce the number of interactions from hundreds of thousands down to hundreds. Have a think about how the maths of every boid considering every other boid scales. Surely not every boid is relevant to every other boid. In short, you probably want to tell clumps of boids to ignore separate clumps of boids until the clumps become relevant to each other. Sort of like what real birds do.
Run the boid routine on each boid every two or three frames, and feather them so they don’t all run on the same frame.
From there, find boids that aren’t going to change direction much and update them even less frequently. Use your flocking manager to find outliers and reduce their update rate.
To do the material thing you update the position of low importance boids in the vertex shader instead of on the CPU. Just send the material instance the directional vector parameter of the boid and the time since the last param update as a scalar parameter. Multiply the direction by the velocity and the amount of time passed and add it to the vertex’s world location. When you actually update the boid’s location on CPU then you can update the params and reset the time that has passed. This will have the effect of having the boid smoothly move in whatever direction it was last going in between CPU updates.
There’s actually one more option that you should look seriously at and it’s a shortcut to computing it on the GPU: use Niagra. You should be able to get location/velocity vector arrays and other bits of flock information back out of it for game rules, but before that it’ll run your entire simulation on the GPU for you and draw it as mesh particles.