-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Events as Entities #2
Comments
Took a crack at this, but ran into a few issues. Overall, it technically works. But it got a little more complex. BackgroundData in Sequentity is organised into a hierarchy with three levels.
Problem 1 - Too much SearchingIn the previous implementation, the struct Event {
entt::entity owner;
};
struct Channel {
entt::entity owner;
std::vector<Event> events;
};
struct Track {
entt::entity owner;
std::vector<Channel> channels;
}; That meant that I could iterate over all channels in a track, and subsequently over each event in a channel by just moving linearly through memory. But because these are now individual entities, with an indirect connection to their "parent", there's quite a bit of searching happening. Searching that may fail and needs synchronisation, for example if a
Line 905 in 5cd803f
The latter approach is what I've done here, and it's not ideal. void Intersect(entt::registry& registry, TimeType time, EntityIntersectFunc func) {
registry.view<Track>().each([&](const auto& track) {
if (track.mute) return;
if (track._notsoloed) return;
for (auto channel_entity : track.children) {
auto& channel = registry.get<Channel>(channel_entity);
for (auto event_entity : channel.children) {
auto& event = registry.get<Event>(event_entity);
if (event.removed) continue;
if (!event.enabled) continue;
if (_contains(event, time)) {
func(track.owner, event_entity);
}
}
}
});
} Is there another way? Problem 2 - Sorting
Events don't necessarily need sorting, as the user can move these around freely, so we do searching both as entities and plain datastructure. Before, the order was determined by their memory layout in e.g. struct Track {
const char* label { "Untitled track" };
entt::entity owner { entt::null };
std::vector<entt::entity> children;
};
...
std::sort(track.children.begin(),track.children.end(), [](const entt::entity lhs,
const entt::entity rhs) -> bool {
return registry.get<Sequentity::Channel>(lhs).type < registry.get<Sequentity::Channel>(rhs).type;
}); SummaryI'm not confident this is the way to go, but may have missed something. Because the data hierarchy is heavily connected and because iterating over the data is consistent with this hierarchy, my gut feeling says this isn't a data-oriented design, but rather an ECS-oriented design. |
Problem
Currently, events are designed to fit with pre-existing entities in your game or application. For example, if your game character is an entity with
Position
andRenderable
components, then you could attach aSequentity::Track
that carry events related to changes to those components.So far so good.
The consequence however is that each Track becomes a "mini-ECS" in that they themselves form a registry of additional entities, each one carrying a
Sequentity::Channel
component, which in turn form yet another registry of theSequentity::Event
. Except they don't carry the advantage of an ECS, in that they are limited to this one component each, and you can't operate on them like you would other entities in your application.Solution
What if each of these were entities of the same registry, associated to your application entity indirectly?
Just your everyday ECS. Now let's get Sequentity in there.
Benefits
void*
inside theEvent
struct itself, and instead rely on EnTT to take ownership and delete any memory alongside removal of the entity..parent
value, rather than physically moving a member of thechannel.events
vector like we must currently.We could also iterate over tracks and channels individually in the same way, if we needed just them and not the events (to draw the outliner, for example). And if we wanted, we could still reach out and fetch the associated channel and track from an event by traversing what is effectively a hierarchy.
Cost
The primary (and poor) reason for not going this route immediately was the percieved performance cost of introducing this hierarchy of enities and traversing between levels.
One of the (hypothetical) advantage of the current data layout is that it is tightly packed.
Each track can be drawn as a whole, with its channels tightly laid out in memory, and its inner events tightly laid out in memory too. Hypotethically, this should be the absolute most optimal way of storing and iterating over the data, and it just so happens to also make for a suitable editing format; e.g. moving a track takes all data with it, moving events in time along a single channel doesn't really make a difference, as the data is all the same and doesn't need to physically move (just need a change to the
.time
integer value).If each event is an entity, then in order to read from its channel we need to perform a separate lookup for its channel. That channel may reside elsewhere in memory, as may its track.
We could potentially sort these three pools such that tracks, its channels and its events reside next to each other, in which case accessing these should be almost as fast (?), except we still need to perform a lookup by entity as opposed to just moving the pointer in a vector.
Aside from this (hypothetical) performance cost however, is there any cost to API or UX? Worth exploring.
The text was updated successfully, but these errors were encountered: