MinecraftForge / MinecraftForge

Modifications to the Minecraft base files to assist in compatibility between mods. New Discord: https://discord.minecraftforge.net/
https://files.minecraftforge.net/
Other
6.93k stars 2.69k forks source link

Add the ability for Parallel Ticks #8610

Open mcenderdragon opened 2 years ago

mcenderdragon commented 2 years ago

Why Parallel ticks ?

with parallel ticks things could get speed up alot. Multithreading will be faster if th threads dont wait for each other to syncronize. Yes, is most cases the nomral tick code can be forther optimized but on servers with large worlds there are many ticking (tile) entities and if even a portion of that can be optimized to tick in parallel that will be a huge improvement.

Curent state

Right now its not possible for mods to implement parralel block ticks on their own without problems regarding read/write access, as well as timig it wih other mods.

Example Idea

Implementing parallel ticks for BlockEntities & nomral Entities is complex and would need breakign changes. I would suggest to first impelement a Multihtreaded TIckEvent simialör to the following:


    @FunctionalInterface
    public static interface IParallelTicker<T, R> extends Function<T, R>
    {

    }

    @FunctionalInterface
    public static interface ISequentialTicker<T, R> extends Function<T, R>
    {

    }

    public static class GatherTicksEvents extends Event
    {
        public final LevelReader level;
        private List<IParallelTicker<LevelReader, Optional<ISequentialTicker<LevelAccessor, Void>>>> tickerList;

        public GatherTicksEvents(LevelReader level) 
        {
            this.level = level;
            tickerList = new ArrayList<>();
        }

        public synchronized void addTicker(IParallelTicker<LevelReader, Optional<ISequentialTicker<LevelAccessor, Void>>> ticker)
        {
            this.tickerList.add(ticker);
        }

        private void doParallelTick(LevelReader level, LevelAccessor fullLevel)
        {
            List<Optional<ISequentialTicker<LevelAccessor, Void>>> sequqentialTicks = tickerList.parallelStream().map(t -> t.apply(level)).filter(Optional::isPresent).toList();
            sequqentialTicks.stream().sequential().forEach(opt -> opt.ifPresent(t -> t.apply(fullLevel)));
        }
    }

    @SubscribeEvent
    public static void tick(GatherTicksEvents event)
    {
        event.addTicker(reader -> {

            // check if recipe of machine is valid

            return Optional.of((accessor -> {

                //increase progress if recipe is valid or reset it;

                return null;
            }));
        });
    }

the IParallelTicker has read only world acces and can then return a ISequentialTicker with full world access to do writing. As the name implies IParallelTicker is executed multithreaded and ISequentialTicker on the main level thread.

If Something like this is worth considering for forge, I would want to implelemnt an example tile Entity and to time test. (I would begin with 100 vanilla furnaces smelting vs 100 Parallel ticking furnaces)

TheCurle commented 2 years ago

The game uses thread-unsafe structures literally everywhere it is possible to. Making the game compatible with multithreading on a level aligned with Forge's compatibility goals would involve fully rewriting the game's physics, rendering, interaction and level storage engines, which is not something we are ever going to do.

If there's some smaller targets that can be verified to actually have a decent benefit and doesn't have patches in almost every single method in the game, then we can consider that.

In its most basic form, this feature is simply not feasible for Forge.

mcenderdragon commented 2 years ago

The game uses thread-unsafe structures literally everywhere it is possible to. Making the game compatible with multithreading on a level aligned with Forge's compatibility goals would involve fully rewriting the game's physics, rendering, interaction and level storage engines, which is not something we are ever going to do.

This is true, thats why read only access is used for parallel ticking, to avoid weird interactions. Also the idea is to add a MultithreadTick event, not make nomral ticking fully multithreaded. So right now the diea would ne 0 patches as it could be done via a simple EventListener for the TickEvent.Level which then fires the does the GatherTicksEvents event and exeutes that in parallel (could also fire the event in parallel).

If there's some smaller targets that can be verified to actually have a decent benefit and doesn't have patches in almost every single method in the game, then we can consider that.

In its most basic form, this feature is simply not feasible for Forge.

Exactly, thats why I was not going to implement something like a parallel BlockEntityTicker but instead a more abstract event.

TheCurle commented 2 years ago

The issue is that nothing can be executed in parallel without causing issues. Even with read-only access, things can change in the world while parallel things are happening, causing logical disconnections and state mismatches.

The game simply isn't equipped to make something like this feasible. Every place the game uses something like this, it ends up being a sequential work queue of some kind. That's no coincidence.

mcenderdragon commented 2 years ago

The issue is that nothing can be executed in parallel without causing issues. Even with read-only access, things can change in the world while parallel things are happening, causing logical disconnections and state mismatches.

As far as I understand in a TickEvent nothing else is ticking so only if in the IParallelTicker the world got changed that would cause problems (which is not allowed and the modder doing that is stupid - thats why only a LevelReader is used and nothing more) . Further the default implementation of .parallelStream() will also use the current thread as a worker as well as all other Worker available in the current Pool. so there would be no other ticking during that time, correct? And after that the sequential ticks would apply changes.

The game simply isn't equipped to make something like this feasible. Every place the game uses something like this, it ends up being a sequential work queue of some kind. That's no coincidence.

Yes to avoid this only fitting things should be done in parallel.

Sm0keySa1m0n commented 2 years ago

I suppose as long as the parallel tickers do not modify the world's state and the main thread waits until they have all ticked before continuing then there shouldn't be any issues.

The main and possibly only advantage to this system (as others have mentioned in Discord) would be doing multiblock checks in parallel as they can be quite time consuming and are read-only operations.

mcenderdragon commented 2 years ago

I suppose as long as the parallel tickers do not modify the world's state and the main thread waits until they have all ticked before continuing then there shouldn't be any issues.

waiting or also exeucting stuff should work.

The main and possibly only advantage to this system (as others have mentioned in Discord) would be doing multiblock checks in parallel as they can be quite time consuming and are read-only operations.

Yes block checks should work good here. Also I think machiens which look up recipes could benefit from here strongly.

RogueLogix commented 2 years ago

The main and possibly only advantage to this system (as others have mentioned in Discord) would be doing multiblock checks in parallel as they can be quite time consuming and are read-only operations.

If your multiblock validation checks are time consuming enough for this to meaningfully matter, you are doing them wrong. Besides it being able to be done extremely quickly, to the extent of being a complete non-issue until you push into kinda absurd sizes (eg: > 250k blocks, approx 64x64x64), validation should also be done very lazily where its not impacting tick time except when the structure is updated (ie: (de)construction chunk(un)load), assuming you are using auto-magic assembly. Additionally, in those cases, there are likely other costs (eg: binary <-> nbt (de)serialization, detachment checking, structure merging) that cant be multi-threaded that are far far higher than the validation cost.

While that isn't true for absolutely every multiblock, it is true for mine as well as the others that I have seen to have a measurable impact on server tick time at construction.