Skip to content

&mut Self in Entity & dynamic Downcasting#227

Draft
unschlagbar wants to merge 153 commits into
Steel-Foundation:masterfrom
unschlagbar:entity_refactor
Draft

&mut Self in Entity & dynamic Downcasting#227
unschlagbar wants to merge 153 commits into
Steel-Foundation:masterfrom
unschlagbar:entity_refactor

Conversation

@unschlagbar

@unschlagbar unschlagbar commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

The goal of the pr

The current entities system tries to avoid deadlocks and a well thought computation order by making all entities completely interior mutable. part of the reason is probably also that most of the devs come from pumpkin where it was practiced similar. The idea is also that all threads can access all entities without having to worry that data may be inaccessible.

Chunk gen in steel is already very fast and very strong parallelized, the speed however comes from the fact that multiple threads can work on there own chunk, there is basically no dependency between the chunks for the heavy part. If multiple threads would work on the same chunk, the sync cost would be almost larger than the multi threading advantage. The same thing applies to entities as well. Entities are a complete unit just like a chunk. for every tick one entities is supposed to only tick from one thread. the interactions between the entities are up to 3 extra locks for complex entities. compared to way over 10 locks and an ugly amount of atomics loads and cell locks.

In my opinion this new entity handling way, is the most important thing that would easily lift steel up to the best mc rust server with a future where writing new and custom entities is just as fun as writing other rust code.

Implementation

SharedEntity is now Arc<EntityBase>. Having an arc here means that this is the root of the entity.
EntityBase now has two more members:

pub struct EntityBase {
   ...
    /// The concrete entity implementation. Empty until `attach_entity` is called.
    entity: OnceLock<Box<SyncMutex<dyn Entity>>>,
    /// Set only for player bases. Like every other entity, a player is reached
    /// mutably by locking; `with_entity`/`with_entity` lock through this weak
    /// reference. Kept separate from the `entity` slot only because the player is
    /// owned by `ServerPlayer`
    player: Weak<SyncMutex<Player>>,
}

For normal Entities the entity member gets the inner entity assign at the initialization. It doesnt need to be an arc since its not the root and the dyn Entity is not supposed to be stored any where. So the entities gets stored as SharedEntities but in fns params its often &mut dyn Entity, just not in the fns where you need a downcast (more on that later).

Players are not owned by the SharedEntity. Since player have parts that can and must be accessed by multiple thread, i moved all these part into a new Struct ServerPlayer (name comes from vanilla). ServerPlayer is the root and owns the player, so thats why SharedEntity only has a weak to it. its not final what is supposed to be in ServerPlayer and what in player (AI decided it for now). Player also has an Weak to ServerPlayer. The goal on that part is to move all network handling to ServerPlayer or maybe even to connection (to replicated vanillas ServerGamePacketListenerImpl) that would also allow for better custom protocols like suggested here: Issue 79.
You can think of Player as the entity part of the player that ticks movement and that stuff, ServerPlayer is the other part that has more power and is interior mutable.

There is a new dowcasting system that removes the need for entity trait methods like: as_experience_orb, as_item_entity.
Its available to use through let entity: Option<T> = entity_base.lock_entity().downcast::<T>(). To verify that self actually is T we take the EntityType of self and compare the identifier to the identifier derive by this macro

for entities where the identifier is different than the class

#[entity_behavior(class = "minecart_chest", identifier = "chest_minecart")]
pub struct ChestMinecartEntity

for entities where its the same

#[entity_behavior(class = "end_crystal")]
pub struct EndCrystalEntity

that also means that it is going to be forbidden, that future mods implement the same entity with the same namespace, just like vanilla.

How to take care of correct order

With this pr, we must make sure for entity x that in its tick function there is no re locking like this self.base().lock_entity() in the entire call chain. That means no all functions in the chain should take &mut dyn Entity if entity specific methods are called otherwise EntityBase is fine.

Todo

Now that i have the diff i should be able to clean every mess up that ai leaved (over 300 entity tests that ai fixed).

The first and best thing is to iterate on this until im satisfied, no idea what exactly needs to be done. Going to update the description once i have it more specifically.

  • Remove every single Mutex from LivingEntityBase, reorder/group members to align with vanilla and change the signature of every affected method to take mut.

  • Bring this pr up to date with Master

@unschlagbar
unschlagbar marked this pull request as draft June 23, 2026 21:11
Brings in 7 upstream commits (cocoa, sponge, rooted dirt, hanging moss,
simple vegetation, non-vanilla view-distance + custom chunk-gen threads,
docker/SWH). Single conflict in server/mod.rs: kept the branch's locked
apply_first_visit_defaults spawn flow and added upstream's chunk-gen
thread-count helpers; dropped upstream's PreparedSpawn/apply_default_spawn
restructuring (incompatible with the branch spawn model). Fixed view-distance
seam in connection.rs (self.config -> self.config()).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

This pull request has conflicts with the base branch "master". Please resolve those so we can test out your changes.

The fix was applied to the working tree but missed the previous merge
commit (it was unstaged), leaving the pushed branch non-compiling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coco875

coco875 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

entity refactor have been merge already

@unschlagbar
unschlagbar changed the base branch from entity_refactor to master June 23, 2026 21:53
@github-actions

Copy link
Copy Markdown

Conflicts have been resolved! 🎉

@github-actions

Copy link
Copy Markdown

This pull request has conflicts with the base branch "master". Please resolve those so we can test out your changes.

@tn-lorenz

Copy link
Copy Markdown
Contributor

Bump, because I really want this to be merged

@coco875

coco875 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

it's marked as draft so not ready to be merged x)

unschlagbar and others added 2 commits July 13, 2026 10:14
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integrates 26 upstream commits (nether/end portals, ender pearls, projectiles,
light, bush block, trapdoors, budding amethyst, bubble columns, POI refactor,
deterministic downcasting, lint enforcement) onto the mutex-free entity model.

Where the two designs collided, upstream's features were ported onto this
branch's architecture rather than taking either side wholesale:

- Entity keeps `base_weak()`/`Arc<EntityBase>`; adds upstream's `ErasedType`
  downcasting but not `+ Send + Sync` (entities hold RefCell/dyn Goal and get
  their sync from the enclosing SyncMutex).
- Entity type names follow this branch (Pig, ExperienceOrb, EndCrystal,
  ItemFrame); upstream's new ender pearl lands as EnderPearl.
- Adopted upstream's removal of the per-entity `random` mutex in favour of
  `rand::random`, which matches this branch's mutex-free goal.
- PlayerRespawnJob holds Arc<ServerPlayer> (Send+Sync) instead of Arc<Player>;
  its finish tails run from the job's tick safe point, preserving the
  "entity unlocked at the tail" property that fixed the respawn deadlocks.
- End credits detach is deferred through the player-reset queue, since it is
  started from the End-portal collision path which already holds the entity lock.
- Chunk-send tracker refresh takes the entity lock only after releasing the
  chunk-sender lock, keeping the lock order acyclic.
- Kept this branch's manager tick gating: players tick in the world game loop,
  not the entity manager (upstream's version double-ticked them).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Conflicts have been resolved! 🎉

@github-actions

Copy link
Copy Markdown

This pull request has conflicts with the base branch "master". Please resolve those so we can test out your changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants