I read that context objects are considered anti-pattern, but don't understand why.
...
I feel it is not a lot better than a global
Seems you do know why then :)
While better than a global, it's still got a foot in the realm of dependency-sphagetti, where you never know which memory regions will be read/written when and by whom. The flow of control and the flow of data are... meandering.
If the flow of control is sticks with each logical system for longer blocks of time (instead of jumping between different objects/tasks constantly) and larger batches are operated on, then not only will single-core performance increase, but it becomes much easier to split work over multiple cores, and IMHO, the data-flow oriented nature of such designs makes your engine easiet to understand /maintain to boot!
Suppose a feature is one kind of entity will play a footstep sound according to the material it is standing on, syncing with its' animation.
this is what I'll do
To get practical with the above advice -
virtual void Entity::Update() hints that your main loop has no knowledge of the flow of data within a frame. It's knowledge of the frame probably consists of: "for each thing, do the stuff".
Instead of this vague view of what happens in a frame, imagine (or draw) a directed-acyclic-graph of all the function calls that occur in this main loop. Color code them by which 'system' they're in (audio/raycast/etc). Then draw out boxes representing all the variables/state in the game, and draw arrows between the graph nodes (function calls) and the data boxes, with arrowheads imdicating reads/writes.
It looks like spaghetti doesnt it? :D
The objective is to make that diagram clean enough to actually have a chance in hell at drawing it on paper. To do that, you need a better main loop than "for each thing, do the stuff".
The first thing your Foo Update does, is read some data from an animation object. This is a temporal data dependency - the animation object must be updated before the Foo object! Easiest way to ensure that is for the game loop to update a list of all animatiojs before updating a list of all Foos.
Drawing the flow between animations::update and foo::update is simple now. Instead of constantly jumping back and forth, there's a big block of animations followed by a big block of foo's. More importantly, the main game loop now knows that it's operating on animations and foos, not abstract entities, so it can pass the specifically required objects into their update functions instead of a bloated context object.
Foo's update also does nothing if an animation event hasnt occurred, which seems likely, so with a lot of foo objects, this is a lot of wasted polling.
Consider using events instead of polling here. When the FooEntity is created, it can register itself with it's animation object, to be notified when a footstep happens. While you're at it, why not use an ID to represent "footstep" instead of hardcoding that feature (data-driven makes it flexible).
Now, when the animstion event (a footstep) occurs, don't go repsonding to the event bu calling Foo::OnAnimationEven right away - that breaks up your batch processing. Instead, have the "for each anim, update anim" loop (maybe called an animation system) produce a list of events that occurred. The Animation::Update function can take just this list as an argument, and push events into it.
After updating all animations, you can sort the list of events by recipiant-type (e.g. some events will go to Foo::OnFootstep, some will go to Bar::OnJump). By sorting the list, you can batch process all Foo anim events back to back, instead of jumping back and forth between Foo logic and Bar logic.
The next thing you do is a raycast. This is typically an expensive operation as it has to read a large amount of world collision data from RAM into CPU cache... So again, we want to batch up as many raycast calls as possible and then execute them all back to back.
Again, build up a queue of raycast requests - Foo::OnFootstep can push a request into this list, along with a function to call if the ray hits something. That function can take the entity and material as parameters.
Later in the frame, you have two loops:
"for each raycast request, cast ray, if hit, record results"
"sort raycast results by callback type"
"foreach raycast result, call callback"
You can then build a list of footstep locations and pass that list onto two functions -- one that spawns new footstep decals, and one that enqueues new footstep sounds.
...etc...
That's one basic pattern anyway... Break up your game frame loop into the actual flow of data that's occurring, and thrn manually schedule the different systems within that data flow, giving each the minimal inputs and outputs.
If you can create a nice data flow directed-acyclic-graph, its super simple to schedule parallel parts of that graph across CPU cores, and get simple multithreading without much extrs thought too.