Page MenuHomeWildfire Games

Introduce a special container-allocator for components.
Changes PlannedPublic

Authored by wraitii on Dec 6 2020, 7:08 PM.

Details

Reviewers
None
Summary

This tackles a similar issue to D1739, but for components, and also it's actually faster.

Currently, the ComponentManager creates component via AllocFunc -> new, then stores pointers in a bunch of places. The key container is m_ComponentsByTypeID.
Instead of allocating these components with new, it's better to use an arena-like allocator. It's probably better to store components of the same type next to each other than the ones of a same entity together.

This introduces a container that's also an allocator, guaranteeing pointer stability & fast access. It's essentially an std::deque<CCmpWhatever> backend with an EntityMap<IComponent*> (rather equivalent to std::unordered_map<entity_id_t, IComponent*>) interface. Except that C++ typing needs this at runtime.

It combines a few advantages:

  • Fast random access, one indirection, similar to a vector. The performance of unordered_map might be better with very high sparsity, I'm not sure.
  • Very good cache locality, as the data is mostly dense (free slots are reused), making for very fast iteration, and generally good performance when accessing the same components of different entities (good for messaging or and various other usage). unordered_map would be slower at iterating, even with arena-backed data, as the iterator isn't in the same place as the data, and there's no guarantee it's iterated in key order. EntityMap would, but it's also sparser, which is bad, particularly for large data.
  • Neat interface, don't have to store a separate allocator (this is slightly arguable, but I think it's neater like that).
  • Like our current approach, pointers are stable, so the component cache can stay the same.

Unfortunately this changes hashes, since the iteration order is not the same as SVN. But on a simple test like just opening COmbat-Demo-Huge and doing nothing, we can measure the raw message overhead, and I'm seeing about 10% improvement in SimUpdate. I would expect the game to be generally slightly faster, particularly as time goes on.

TODO:

  • Verify that the sparse key index is actually faster than unordered_map, as with some sparsity that might actually change.
  • Run some more profiling
  • Add tests.
Test Plan

Compile, profile, review the container, it's still a bit quirky I think.

Diff Detail

Event Timeline

wraitii created this revision.Dec 6 2020, 7:08 PM
Vulcan added a comment.Dec 6 2020, 7:10 PM

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/vs2015-differential/3392/display/redirect

Vulcan added a comment.Dec 6 2020, 7:12 PM

Build failure - The Moirai have given mortals hearts that can endure.

builderr-debug-gcc7.txt
In file included from ../../../source/simulation2/system/ComponentManager.h:23,
                 from ../../../source/simulation2/system/Component.h:23,
                 from ../../../source/pch/simulation2/precompiled.h:29:
../../../source/simulation2/helpers/FlatMap.h:38:51: error: expected template-name before '<' token
 class StablePool : public Allocators::DynamicArena<BLOCK_SIZE>
                                                   ^
../../../source/simulation2/helpers/FlatMap.h:38:51: error: expected '{' before '<' token
make[1]: *** [simulation2.make:236: obj/simulation2_Debug/precompiled.h.gch] Error 1
make: *** [Makefile:107: simulation2] Error 2

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/3962/display/redirect

wraitii published this revision for review.Dec 6 2020, 9:26 PM
wraitii planned changes to this revision.Dec 14 2020, 10:37 AM

This is probably super-broken if there is ever an offset between the component pointer and IComponent*, that I need to address.

Also need to address a few other tweaks here and there for niceness.

In my testing, this behaved rather performantly.