Skip to content
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

[1.21.1+] Singleplayer World crashes due to Misaligned Registries after Multiplayer World Sync #1932

Open
ChampionAsh5357 opened this issue Jan 31, 2025 · 0 comments · May be fixed by #1934
Labels
1.21 Targeted at Minecraft 1.21 1.21.1 Targeted at Minecraft 1.21.1 1.21.2 Targeted at Minecraft 1.21.2 1.21.3 Targeted at Minecraft 1.21.3 1.21.4 Targeted at Minecraft 1.21.4 1.21.5 Targeted at Minecraft 1.21.5 bug A bug or error

Comments

@ChampionAsh5357
Copy link
Contributor

Minecraft Version: 1.21.1+, likely prior versions

NeoForge Version: 21.0.0-beta+

Original Issue: mezz/JustEnoughItems#3905 (Thank you to @pietro-lopes for the initial report and research)

Steps to Reproduce:

  1. Create a simple mod that registers something to one of the specified registries in NeoForgeRegistriesSetup#VANILLA_SYNC_REGISTRIES only on the physical client.
@Mod("examplemod")
public class ExampleMod {

    public ExampleMod(IEventBus modBus, Dist dist) {

        if (dist == Dist.CLIENT) {
            var test = DeferredRegister.create(Registries.RECIPE_SERIALIZER, "examplemod");
            test.register(modBus);
            // Using a dummy as it doesn't matter since it's not used anywhere in the codebase
            test.register("test", () -> new AbstractCookingRecipe.Serializer<>(CampfireCookingRecipe::new, 100));
        }
    }
}
  1. Run a client and dedicated server (or an integrated server on a separate client).
  2. Create a new singleplayer world and log in (doesn't matter what settings).
  3. Log into the dedicated server from the client using the Multiplayer tab.
  4. Disconnect through the pause screen.
  5. Log into the singleplayer world again and notice the ArrayIndexOutOfBoundsException thrown during registry sync from the integrated server.

Description of issue:

This bug results from a stale reference to multiplayer synced registries when the physical client has an additional registry object registered.

When the RegistryManager takes a snapshot of some registry and applies it to the existing registered, there needs to be some method to sync the identifiers. However, we generally don't want to overwrite the entire registry itself, instead, only modifying the network used integer ids to match those on the server. To do so, the RegistryManager when constructing the registries to sync to the client, clears the ids to be repopulated via RegistryManager#applySnapshot via MappedRegistry#registerIdMapping.

Note that MappedRegistry#clear(false) (which is what's called when syncing registries) only clears the integer identifiers, not the resource keys. However, when the server has a subset of the client registry objects, that is fine. The constructed RegistrySnapshot when calling MappedRegistry#getId for the ResourceLocation of the registry objects will be able to return the available ids.

Now comes the problem, if we are replacing the identifiers on every world load, that would mean, depending on the world, there could be a differing number of entries between the server and client (assuming that there is no aliasing or other shenanigans going on). Well, not to worry! The RegistryManager takes a snapshot of the physical client registries after the RegisterEvents are finished. Then, whenever a world disconnects, either via ServerLifecycleHooks#handleServerStopped or ClientCommonPacketListenerImpl#onDisconnect, this snapshot is applied (via RegistryManager#revertToFrozen) to revert back to the state on the physical client.

So, what's the issue? Well, ServerLifecycleHooks#handleServerStopped is only called when a singleplayer world is exited, which ClientCommonPacketListenerImpl#onDisconnect is called when a client disconnects for an unexpected reason from the multiplayer server. It does not cover the case when the client clicks the disconnect button and disconnects normally from the multiplayer server. As such, the registry snapshot from the multiplayer server is still stored on the physical client. So, if let's say, the client has one more registry object than the server, when loading a singleplayer world, it will attempt to get the integer id for a key that was scrubbed by the multiplayer server.

The simplest way to fix this issue is to call RegistryManager#revertToFrozen at the end of Minecraft#disconnect as all instances of revertToFrozen are directly called after disconnect or once the integrated server is finished shutting down, which disconnect waits for anyway.

@ChampionAsh5357 ChampionAsh5357 added 1.21 Targeted at Minecraft 1.21 1.21.1 Targeted at Minecraft 1.21.1 1.21.2 Targeted at Minecraft 1.21.2 1.21.3 Targeted at Minecraft 1.21.3 1.21.4 Targeted at Minecraft 1.21.4 1.21.5 Targeted at Minecraft 1.21.5 bug A bug or error request for comments For gathering opinions on some topic or subject and removed request for comments For gathering opinions on some topic or subject labels Jan 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1.21 Targeted at Minecraft 1.21 1.21.1 Targeted at Minecraft 1.21.1 1.21.2 Targeted at Minecraft 1.21.2 1.21.3 Targeted at Minecraft 1.21.3 1.21.4 Targeted at Minecraft 1.21.4 1.21.5 Targeted at Minecraft 1.21.5 bug A bug or error
Projects
None yet
1 participant