Rewrite the project with `evenio`
rj00a opened this issue · 0 comments
For the past few months I have been working on a little ECS library called evenio
. This is in response to some limitations I have encountered using bevy_ecs
in Valence. In essence, I believe that "run once per frame" systems as the primary abstraction is fundamentally inadequate. This one fact has been the source of many design issues in Valence. Here are some of the problems and how evenio
could fix them:
-
In bevy_ecs, there aren't any reliable ways to enforce that entities have certain combinations of components because there's always a delay between when the violation occurs and when it can be acted on. As a result, we don't even attempt to detect these potential mistakes.
evenio
lets us write handlers that run the moment before a component is added or removed, making these violations feasible to detect and act on. -
Whenever an entity is despawned we almost always need to do some cleanup work. Bevy lets us detect that an entity is removed from the world, but doesn't give access to the entity's components because the entity is already gone. We work around this by using a
Despawned
component to signal removal, then letting systems inPostUpdate
run before it's removed. This works, but it's very error prone because there's nothing stopping someone from calling.despawn()
instead of adding the marker.evenio
fixes this problem by signaling entity despawns with an event, allowing code to run immediately before the entity is removed. -
A lot of valence's APIs are based on a pattern where we let the user make some changes to the world in
Update
and then defer handling the changes untilPostUpdate
. Compared to the simple procedural approach, This is a lot more cumbersome. Case in point, I tried to improve layers and clean things up in #533 but the resulting code was so complicated and error prone that I gave up. Inevenio
, we can just use events to signal state changes instead of using change detection or diffing. -
While reading packets from clients, we split the stream of packets into separate event buffers based on packet type. The problem is, this loses the order between different packet types. To get around this, I made an entirely separate schedule which runs in a loop to ensure that packet handler code is executed in the correct order. This is ugly and slow. #587 tried to simplify this a bit without being aware of these details. We could have easily fixed this by using system callbacks, but this wouldn't really integrate with bevy's schedules. In
evenio
, event handlers are run in the order events are produced (regardless of type) so the problem goes away. -
Every system added in Bevy has a cost. Systems run every frame even if they have no work to do. This is especially bad with the multithreaded executor due to the overhead involved. #571 tried to fix the entity extractor and entity tracked data, but the solution would have caused an explosion in the number of systems. With
evenio
, systems don't have a cost unless the event they're listening for is actually sent. -
In #323 I noticed poor performance while the server isn't doing anything. Likely related to the previous point.
-
Bevy's change detection feature is not flexible enough. Sometimes it works fine, but in other cases we end up using
.bypass_change_detection()
which results in footguns for end users. -
evenio
doesn't have system order ambiguities, so #542 goes away. -
#399 wants to introduce cancellable events similar to bukkit. This might be a lot easier with
evenio
events.
So, here's my plan. Let's burn it all to the ground and start over with evenio
. We can leave valence_protocol
and everything below it mostly intact, but everything else needs to go. Although evenio
needs quite a lot of work before I would call it production ready, it should be usable enough to get started. Bevy has recently adopted some features like one-shot systems to address the above problems, but I don't really think they get at the root of the issue.
During this rewrite I would like to do a few other things:
- Update to the latest MC version (assuming #594 doesn't do it first).
- Use completion-based IO for better perf (#489).
- Aggressively cut out unused dependencies. Compile times are too long.
- CI needs to be cleaned up.
- Actually handle timesteps correctly (#327).
I have started work in #599