Last active
January 3, 2022 01:20
-
-
Save mdragon159/b82c3d46ea9f3c310cb74feb1987b025 to your computer and use it in GitHub Desktop.
Sample EnTT Snapshot Creation & Restoration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Macro workaround to having to define code for every single component | |
// Just need to remember to define all Component classes here as well as in Components.h | |
// References: | |
// https://stackoverflow.com/questions/25428144/iterate-a-preprocessor-macro-on-a-list-of-class-names | |
// https://stackoverflow.com/questions/6635851/real-world-use-of-x-macros | |
// https://en.wikibooks.org/wiki/C_Programming/Preprocessor_directives_and_macros#X-Macros | |
// Also yes, file name + type is arbitrary | |
// Note that yes, macros should be a last resort. If there's a better way to arbitrarily define list of classes in one place | |
// and freely use elsewhere to generate arbitrary repetitive code, then would be nice to replace this approach here | |
// Sample usage: | |
// #define COMPONENT_CLASS(classname) mSimContext.logWarnMessage("Example", "Class name: ##classname"); | |
// #include "GameCore/Components/Components.def" | |
////////////////////////////////////////////////////////////////////////////////////////////////// | |
#ifndef COMPONENT_CLASS | |
#error COMPONENT_CLASS should be defined before including Components.def | |
#endif | |
// Define list of classes. Using "COMPONENT_CLASS" as arbitrary macro that is expected to not be used outside of this context | |
COMPONENT_CLASS(TransformComponent) | |
COMPONENT_CLASS(PhysicsComponent) | |
COMPONENT_CLASS(DynamicColliderComponent) | |
COMPONENT_CLASS(StaticColliderComponent) | |
COMPONENT_CLASS(SpawnVolumeComponent) | |
COMPONENT_CLASS(LocalPlayerInputsComponent) | |
COMPONENT_CLASS(CameraTransformComponent) | |
COMPONENT_CLASS(HumanoidStateComponent) | |
COMPONENT_CLASS(MovementInputsComponent) | |
COMPONENT_CLASS(MovementStateComponent) | |
COMPONENT_CLASS(AttackChargeActionComponent) | |
COMPONENT_CLASS(AttackActionComponent) | |
COMPONENT_CLASS(QuickStepActionComponent) | |
COMPONENT_CLASS(GrappleAimComponent) | |
COMPONENT_CLASS(GrappleActionComponent) | |
COMPONENT_CLASS(GrappleQuickPullComponent) | |
COMPONENT_CLASS(HitfreezeComponent) | |
COMPONENT_CLASS(HitstunComponent) | |
COMPONENT_CLASS(DeathComponent) | |
COMPONENT_CLASS(EnemyComponent) | |
COMPONENT_CLASS(HealthComponent) | |
COMPONENT_CLASS(ActiveGrappleProjectileComponent) | |
COMPONENT_CLASS(UnrealActorFeedbackComponent) | |
#undef COMPONENT_CLASS |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class GameplayManager { | |
entt::registry registry; | |
bool isSnapshotStored = false; | |
// ECS Snapshot: Create vector of entities and components per component class, as EnTT stores data on a per-component-class basis | |
#define COMPONENT_CLASS(classname) std::vector<entt::entity> entitySnapshot_##classname; \ | |
std::vector<classname> componentSnapshot_##classname; | |
#include "GameCore/Components/Components.def" | |
// Additional snapshot data for proper entity restoration | |
entt::entity snapshotReleased; | |
std::size_t snapshotRegistrySize; | |
std::vector<entt::entity> snapshotRegistryEntities; | |
public: | |
//........ | |
void testSnapshotCreationAndRestoration() { | |
// Begin measuring time for performance stat | |
uint64_t actionStartTimeInUs = SharedUtilities::getTimeInMicroseconds(); | |
std::string actionName; | |
// If snapshot not created yet, create one from current frame's data | |
if (!isSnapshotStored) { | |
actionName = "SnapshotCreation6"; | |
isSnapshotStored = true; | |
createSnapshot(); | |
} | |
// Otherwise, restore from the existing snapshot | |
else { | |
actionName = "SnapshotRestoration6"; | |
isSnapshotStored = false; // For easy toggle testing, treat current state as if no snapshot stored | |
restoreSnapshot(); | |
} | |
// Output runtime performance stat | |
uint64_t actionRunTimeInUs = SharedUtilities::getTimeInMicroseconds() - actionStartTimeInUs; | |
// .....Placeholder to log or otherwise use the above result | |
} | |
private: | |
void createSnapshot() { | |
snapshotRegistrySize = registry.size(); | |
snapshotReleased = registry.released(); | |
snapshotRegistryEntities.insert(snapshotRegistryEntities.end(), registry.data(), registry.data() + snapshotRegistrySize); | |
// Store the actual snapshot data per each component class | |
// This copy-per-class approach is necessary due to how EnTT is built | |
#define COMPONENT_CLASS(classname) createSnapshotForComponent<classname>(entitySnapshot_##classname, componentSnapshot_##classname); | |
#include "GameCore/Components/Components.def" | |
} | |
template<typename ComponentType> | |
void createSnapshotForComponent(std::vector<entt::entity>& entitySnapshot, std::vector<ComponentType>& componentSnapshot) { | |
// Get reference to unique storage for this component type | |
auto&& storage = registry.storage<ComponentType>(); | |
// Create entity snapshot | |
// For why not using memcpy (speed) and examples of different methods for copying data into a vector: https://stackoverflow.com/a/261607/3735890 | |
entitySnapshot.insert(entitySnapshot.end(), storage.data(), storage.data() + storage.size()); | |
// Create component snapshot | |
// Note that data may cross multiple pages in memory. Thus, need to copy the data into the output vector one page at a time | |
const std::size_t pageSize = entt::component_traits<ComponentType>::page_size; | |
const std::size_t totalPages = (storage.size() + pageSize - 1u) / pageSize; | |
for(std::size_t pageIndex{}; pageIndex < totalPages; pageIndex++) { | |
// Calculate number of elements to copy so only copyyig over the necessary number of elements | |
// Truly necessary as using ComponentType vector here instead of, say, vector of arbitrary bytes | |
const std::size_t offset = pageIndex * pageSize; | |
const std::size_t numberOfElementsToCopy = std::min(pageSize, storage.size() - offset); | |
// Do the actual copying | |
ComponentType* pageStartPtr = storage.raw()[pageIndex]; | |
componentSnapshot.insert(componentSnapshot.end(), pageStartPtr, pageStartPtr + numberOfElementsToCopy); | |
} | |
} | |
void restoreSnapshot() { | |
registry = {}; | |
registry.assign(snapshotRegistryEntities.data(), snapshotRegistryEntities.data() + snapshotRegistrySize, snapshotReleased); | |
// Restore the actual snapshot data per each component class | |
// This copy-per-class approach is necessary due to how EnTT is built | |
#define COMPONENT_CLASS(classname) restoreSnapshotForComponent<classname>(entitySnapshot_##classname, componentSnapshot_##classname); | |
#include "GameCore/Components/Components.def" | |
cleanUpSnapshotData(); | |
} | |
template<typename ComponentType> | |
void restoreSnapshotForComponent(std::vector<entt::entity>& entitySnapshot, std::vector<ComponentType>& componentSnapshot) { | |
// Get reference to unique storage for this component type | |
auto&& storage = registry.storage<ComponentType>(); | |
// Restore entities | |
storage.insert(entitySnapshot.begin(), entitySnapshot.end()); | |
// Restore components | |
// Note that data may cross multiple pages in memory. Thus, need to copy the data into the storage one page at a time | |
const std::size_t pageSize = entt::component_traits<ComponentType>::page_size; | |
const std::size_t totalPages = (storage.size() + pageSize - 1u) / pageSize; | |
for(std::size_t pageIndex{}; pageIndex < totalPages; pageIndex++) { | |
const std::size_t offset = pageIndex * pageSize; | |
const std::size_t numberOfElementsToCopy = std::min(pageSize, componentSnapshot.size() - offset); | |
ComponentType* pageStartPtr = storage.raw()[pageIndex]; | |
memcpy(pageStartPtr, componentSnapshot.data() + offset, sizeof(ComponentType) * numberOfElementsToCopy); | |
} | |
} | |
void cleanUpSnapshotData() { | |
snapshotRegistryEntities.clear(); | |
// Clear all existing snapshot data | |
#define COMPONENT_CLASS(classname) cleanUpSnapshotData<classname>(entitySnapshot_##classname, componentSnapshot_##classname); | |
#include "GameCore/Components/Components.def" | |
} | |
template<typename ComponentType> | |
void cleanUpSnapshotData(std::vector<entt::entity>& entitySnapshot, std::vector<ComponentType>& componentSnapshot) { | |
entitySnapshot.clear(); | |
componentSnapshot.clear(); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment