From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: 2No2Name <50278648+2No2Name@users.noreply.github.com>
Date: Sun, 21 Feb 2021 21:34:18 -0500
Subject: [PATCH] lithium: skip ticking block entities that are doing nothing


diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningList.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningList.java
new file mode 100644
index 0000000000000000000000000000000000000000..edcc9ec27cc7d8dc5bf04e1f70362b505742570c
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/ListeningList.java
@@ -0,0 +1,255 @@
+package me.jellysquid.mods.lithium.common.util.collections;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+
+public class ListeningList<T> implements List<T> {
+    private final List<T> delegate;
+    private final Runnable changeCallback;
+
+    public ListeningList(List<T> delegate, Runnable changeCallback) {
+        this.delegate = delegate;
+        this.changeCallback = changeCallback;
+    }
+
+    private void onChange() {
+        this.changeCallback.run();
+    }
+
+
+    @Override
+    public int size() {
+        return this.delegate.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return this.delegate.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return this.delegate.contains(o);
+    }
+
+    @NotNull
+    @Override
+    public Iterator<T> iterator() {
+        return this.listIterator();
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+        return this.delegate.toArray();
+    }
+
+    @Override
+    public void forEach(Consumer<? super T> consumer) {
+        this.delegate.forEach(consumer);
+    }
+
+    @NotNull
+    @Override
+    public <T1> T1[] toArray(@NotNull T1[] t1s) {
+        //noinspection SuspiciousToArrayCall
+        return this.delegate.toArray(t1s);
+    }
+
+    @Override
+    public boolean add(T t) {
+        boolean add = this.delegate.add(t);
+        this.onChange();
+        //noinspection ConstantConditions
+        return add;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        boolean remove = this.delegate.remove(o);
+        this.onChange();
+        return remove;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> collection) {
+        return this.delegate.containsAll(collection);
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends T> collection) {
+        boolean addAll = this.delegate.addAll(collection);
+        this.onChange();
+        return addAll;
+    }
+
+    @Override
+    public boolean addAll(int i, @NotNull Collection<? extends T> collection) {
+        boolean addAll = this.delegate.addAll(i, collection);
+        this.onChange();
+        return addAll;
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> collection) {
+        boolean b = this.delegate.removeAll(collection);
+        this.onChange();
+        return b;
+    }
+
+    @Override
+    public boolean removeIf(Predicate<? super T> predicate) {
+        boolean b = this.delegate.removeIf(predicate);
+        this.onChange();
+        return b;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> collection) {
+        boolean b = this.delegate.retainAll(collection);
+        this.onChange();
+        return b;
+    }
+
+    @Override
+    public void replaceAll(UnaryOperator<T> unaryOperator) {
+        this.delegate.replaceAll( unaryOperator);
+        this.onChange();
+    }
+
+    @Override
+    public void sort(Comparator<? super T> comparator) {
+        this.delegate.sort(comparator);
+        this.onChange();
+    }
+
+    @Override
+    public void clear() {
+        this.delegate.clear();
+        this.onChange();
+    }
+
+    @Override
+    public T get(int i) {
+        return this.delegate.get(i);
+    }
+
+    @Override
+    public T set(int i, T t) {
+        T set = this.delegate.set(i, t);
+        this.onChange();
+        return set;
+    }
+
+    @Override
+    public void add(int i, T t) {
+        this.delegate.add(i, t);
+        this.onChange();
+    }
+
+    @Override
+    public T remove(int i) {
+        T remove = this.delegate.remove(i);
+        this.onChange();
+        return remove;
+    }
+
+    @Override
+    public int indexOf(Object o) {
+        return this.delegate.indexOf(o);
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+        return this.delegate.lastIndexOf(o);
+    }
+
+    @NotNull
+    @Override
+    public ListIterator<T> listIterator() {
+        return this.listIterator(0);
+    }
+
+    @NotNull
+    @Override
+    public ListIterator<T> listIterator(int i) {
+        return new ListIterator<T>() {
+            final ListIterator<T> itDelegate = ListeningList.this.delegate.listIterator(i);
+
+            @Override
+            public boolean hasNext() {
+                return this.itDelegate.hasNext();
+            }
+
+            @Override
+            public T next() {
+                return this.itDelegate.next();
+            }
+
+            @Override
+            public boolean hasPrevious() {
+                return this.itDelegate.hasPrevious();
+            }
+
+            @Override
+            public T previous() {
+                return this.itDelegate.previous();
+            }
+
+            @Override
+            public int nextIndex() {
+                return this.itDelegate.nextIndex();
+            }
+
+            @Override
+            public int previousIndex() {
+                return this.itDelegate.previousIndex();
+            }
+
+            @Override
+            public void remove() {
+                this.itDelegate.remove();
+                ListeningList.this.onChange();
+            }
+
+            @Override
+            public void set(T t) {
+                this.itDelegate.set(t);
+                ListeningList.this.onChange();
+
+            }
+
+            @Override
+            public void add(T t) {
+                this.itDelegate.add(t);
+                ListeningList.this.onChange();
+            }
+        };
+    }
+
+    @NotNull
+    @Override
+    public List<T> subList(int i, int i1) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Spliterator<T> spliterator() {
+        return this.delegate.spliterator();
+    }
+
+    @Override
+    public Stream<T> stream() {
+        return this.delegate.stream();
+    }
+
+    @Override
+    public Stream<T> parallelStream() {
+        return this.delegate.parallelStream();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedTickingBlockEntityList.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedTickingBlockEntityList.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f7e4810a9aca382d75862e56b0cf93157b54b92
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/MaskedTickingBlockEntityList.java
@@ -0,0 +1,320 @@
+package me.jellysquid.mods.lithium.common.util.collections;
+
+import com.google.common.collect.Iterators;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.function.Predicate;
+
+public class MaskedTickingBlockEntityList<T> implements List<T> {
+    private final Predicate<T> mayContain;
+
+    private final Reference2IntOpenHashMap<T> allElements2Index;
+    private final ReferenceArrayList<T> allElements;
+
+    private final IntArrayList filteredSuccessor;
+    private final BitSet filteredElementsMask;
+
+    private int firstRemovedIndex;
+
+    //Visualization of the internal datastructures
+    //indices:      0 1 2 3 4 5 6 7 8 9
+    //allElements:  A B C D - F G H I J  //E was fully removed, C,F,G,I were filtered away
+    //filteredMask: 1 1 0 1 0 0 0 1 0 1
+    //successor:  0 1 3 - 7 - - - 9 - - (index offset by 1, due to the first successor having to point to the first element)
+    //Removals from the allElements ArrayList are done by setting the value to null
+    //The successor list is used to iterate through the allElements ArrayList with an increasing index, but skipping long chains of null elements.
+    //The BitSet mask is used to find the predecessor and successor quickly (not asymptotically fast, but fast enough)
+
+    public MaskedTickingBlockEntityList(List<T> list, Predicate<T> mayContain) {
+        this.mayContain = mayContain;
+        this.allElements = new ReferenceArrayList<>();
+        this.allElements2Index = new Reference2IntOpenHashMap<>();
+        this.allElements2Index.defaultReturnValue(-1);
+        this.filteredSuccessor = new IntArrayList();
+        this.filteredElementsMask = new BitSet();
+        this.firstRemovedIndex = Integer.MAX_VALUE;
+
+        for (T t : list) {
+            if (this.mayContain.test(t)) {
+                int index = this.allElements.size();
+                this.allElements.add(t);
+                this.filteredElementsMask.set(index);
+                this.filteredSuccessor.add(index);
+                this.allElements2Index.put(t, index);
+            }
+        }
+        this.filteredSuccessor.add(-1);
+    }
+
+    public void setEntryVisible(T t, boolean value) {
+        this.setEntryVisible(this.allElements2Index.getOrDefault(t, -1), value);
+    }
+
+    public void setEntryVisible(int index, boolean value) {
+        //Visualization of the operations possible
+        //All:         A B C D - F G H I J (- for null)
+        //filteredMask:1 1 0 1 0 0 0 1 0 1
+        //indices:     0 1 2 3 4 5 6 7 8 9
+        //successor: 0 1 3 - 7 - - - 9 - - (- for no successor)
+        //Set F visible:
+        //All:         A B C D - F G H I J
+        //filteredMask:1 1 0 1 0 1 0 1 0 1  //set mask at F to 1
+        //indices:     0 1 2 3 4 5 6 7 8 9
+        //successor: 0 1 3 - 5 - 7 - 9 - -  //update successor of predecessor to F and set F's successor to old successor of predecessor
+        //Set D filtered:
+        //All:         A B C D - F G H I J
+        //Mask:        1 1 0 0 0 1 0 1 0 1  //set mask at D to 0
+        //indices:     0 1 2 3 4 5 6 7 8 9
+        //successor: 0 1 5 - - - 7 - 9 - -  //update successor of predecessor to old successor of D and remove D's successor value
+
+        //These calls do not modify the size, they can't cause rehashing, they are safe to use during iteration
+        if (index == -1 || value == this.filteredElementsMask.get(index)) {
+            return;
+        }
+
+        this.filteredElementsMask.set(index, value);
+        int predecessor = this.filteredElementsMask.previousSetBit(index - 1);
+        if (value) {
+            int successor = this.filteredSuccessor.getInt(predecessor + 1);
+            this.filteredSuccessor.set(predecessor + 1, index);
+            this.filteredSuccessor.set(index + 1, successor);
+        } else {
+            int successor = this.filteredSuccessor.getInt(index + 1);
+            this.filteredSuccessor.set(predecessor + 1, successor);
+            this.filteredSuccessor.set(index + 1, -2); //no successor as this element cannot be reached
+        }
+    }
+
+    private void compact() {
+        int targetSize = this.size();
+        int newIndex = this.firstRemovedIndex - 1;
+        int lastVisible = this.filteredElementsMask.previousSetBit(newIndex);
+
+
+        for (int i = newIndex + 1; i < this.allElements.size(); i++) {
+            T t = this.allElements.get(i);
+            if (t == null) {
+                continue;
+            }
+            boolean visible = this.filteredElementsMask.get(i);
+            //shift all entries to the lower indices (filling the gaps created by remove() setting null)
+            newIndex++;
+            //i is guaranteed to not be smaller than newIndex, therefore we can write to the same collections
+
+            this.allElements.set(newIndex, t);
+            this.allElements2Index.put(t, newIndex);
+            this.filteredElementsMask.set(newIndex, visible);
+
+            //update the successor links
+            this.filteredSuccessor.set(newIndex + 1, -2); //no successor as there is no next entry yet
+            if (visible) {
+                this.filteredSuccessor.set(lastVisible + 1, newIndex);
+                lastVisible = newIndex;
+            }
+        }
+
+        if (newIndex + 1 != targetSize) {
+            throw new IllegalStateException("Compaction ended up with incorrect size: Should be: " + targetSize + " but is: " + (newIndex + 1));
+        }
+
+        this.filteredSuccessor.set(lastVisible + 1, -1); //-1 means this was the last element
+        this.firstRemovedIndex = Integer.MAX_VALUE;
+
+        this.filteredSuccessor.removeElements(targetSize + 1, this.filteredSuccessor.size());
+        this.allElements.removeElements(targetSize, this.allElements.size());
+        this.filteredElementsMask.clear(targetSize, this.filteredElementsMask.size());
+
+        this.filteredSuccessor.trim(targetSize * 4);
+        this.allElements.trim(targetSize * 4);
+        this.allElements2Index.trim(targetSize * 4);
+    }
+
+    public Iterator<T> filteredIterator() {
+        return new Iterator<T>() {
+            int next = MaskedTickingBlockEntityList.this.filteredSuccessor.getInt(0);
+            T prev;
+
+            @Override
+            public boolean hasNext() {
+                return this.next != -1;
+            }
+
+            @Override
+            public T next() {
+                int next = this.next;
+                T prev = MaskedTickingBlockEntityList.this.allElements.get(next);
+                this.prev = prev;
+                this.next = MaskedTickingBlockEntityList.this.filteredSuccessor.getInt(next + 1);
+                return prev;
+            }
+
+            @Override
+            public void remove() {
+                MaskedTickingBlockEntityList.this.remove(this.prev);
+            }
+        };
+    }
+
+    @Override
+    public int size() {
+        return this.allElements2Index.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return this.size() == 0;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        //noinspection SuspiciousMethodCalls
+        return this.allElements2Index.containsKey(o);
+    }
+
+    @Override
+    public @NotNull Iterator<T> iterator() {
+        return Iterators.unmodifiableIterator(this.allElements2Index.keySet().iterator());
+    }
+
+    @Override
+    public Object[] toArray() {
+        return this.allElements2Index.keySet().toArray();
+    }
+
+    @NotNull
+    @Override
+    public <T1> T1[] toArray(@NotNull T1[] t1s) {
+        //noinspection SuspiciousToArrayCall
+        return this.allElements2Index.keySet().toArray(t1s);
+    }
+
+    @Override
+    public boolean add(T t) {
+        int arraySize = this.allElements.size();
+        int invalidEntries = arraySize - this.size();
+        //Compaction is done during the add operation as it is guaranteed to not happen during iteration
+        if ((arraySize > 2048 && invalidEntries > (arraySize >> 1)) || arraySize >= Integer.MAX_VALUE - 1 && invalidEntries != 0) {
+            this.compact();
+        }
+
+        if (!this.mayContain.test(t)) {
+            return false;
+        }
+
+        int index = this.allElements.size();
+        int i = this.allElements2Index.putIfAbsent(t, index);
+        if (i != -1) {
+            return false;
+        }
+        this.allElements.add(t);
+        this.filteredSuccessor.add(0);//increase size so setEntryVisible doesn't crash with indexOutOfBounds
+        this.setEntryVisible(index, true);
+
+        return true;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        int index = this.allElements2Index.removeInt(o);
+        if (index == -1) {
+            return false;
+        }
+        this.setEntryVisible(index, false);
+        this.allElements.set(index, null);
+        this.firstRemovedIndex = Math.min(this.firstRemovedIndex, index);
+        return true;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> c) {
+        return this.allElements2Index.keySet().containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends T> c) {
+        boolean b = false;
+        for (T t : c) {
+            this.add(t);
+            b = true;
+        }
+        return b;
+    }
+
+    @Override
+    public boolean addAll(int index, @NotNull Collection<? extends T> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> c) {
+        boolean b = false;
+        for (Object t : c) {
+            b |= this.remove(t);
+        }
+        return b;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+        this.allElements2Index.clear();
+        this.allElements.clear();
+        this.filteredSuccessor.clear();
+        this.filteredElementsMask.clear();
+        this.firstRemovedIndex = Integer.MAX_VALUE;
+        this.filteredSuccessor.add(-1);
+    }
+
+    @Override
+    public T get(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public T set(int index, T element) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void add(int index, T element) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public T remove(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int indexOf(Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public @NotNull ListIterator<T> listIterator() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public @NotNull ListIterator<T> listIterator(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public @NotNull List<T> subList(int fromIndex, int toIndex) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/BlockEntitySleepTracker.java b/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/BlockEntitySleepTracker.java
new file mode 100644
index 0000000000000000000000000000000000000000..c54e1e1f9c6b09022c0f20d5ea4d552bd3ceed55
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/BlockEntitySleepTracker.java
@@ -0,0 +1,7 @@
+package me.jellysquid.mods.lithium.common.world.blockentity;
+
+import net.minecraft.world.level.block.entity.TileEntity;
+
+public interface BlockEntitySleepTracker {
+    void setAwake(TileEntity tileEntity, boolean needsTicking);
+}
\ No newline at end of file
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/SleepingBlockEntity.java b/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/SleepingBlockEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e765ab19ffb300b6c810333b2dc797637ae5c1b
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/world/blockentity/SleepingBlockEntity.java
@@ -0,0 +1,7 @@
+package me.jellysquid.mods.lithium.common.world.blockentity;
+
+public interface SleepingBlockEntity {
+    default boolean canTickOnSide(boolean isClient) {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java
index f5badbe0dee5c40cf83a5d2993d27ed70ddd2c85..a23c5172857a003b546e33d2935492e3260be560 100644
--- a/src/main/java/net/minecraft/world/level/World.java
+++ b/src/main/java/net/minecraft/world/level/World.java
@@ -97,8 +97,11 @@ import org.bukkit.event.block.BlockPhysicsEvent;
 // CraftBukkit end
 
 import net.gegy1000.tictacs.NonBlockingWorldAccess; // Yatopia
+import me.jellysquid.mods.lithium.common.util.collections.MaskedTickingBlockEntityList; // Yatopia
+import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; // Yatopia
+import me.jellysquid.mods.lithium.common.world.blockentity.SleepingBlockEntity; // Yatopia
 
-public abstract class World implements GeneratorAccess, AutoCloseable, NonBlockingWorldAccess { // Yatopia
+public abstract class World implements GeneratorAccess, AutoCloseable, NonBlockingWorldAccess, BlockEntitySleepTracker { // Yatopia
 
     protected static final Logger LOGGER = LogManager.getLogger();
     public static final Codec<ResourceKey<World>> f = MinecraftKey.a.xmap(ResourceKey.b(IRegistry.L), ResourceKey::a);
@@ -107,9 +110,17 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki
     public static final ResourceKey<World> THE_END = ResourceKey.a(IRegistry.L, new MinecraftKey("the_end"));
     private static final EnumDirection[] a = EnumDirection.values();
     //public final List<TileEntity> tileEntityList = Lists.newArrayList(); // Paper - remove unused list
-    public final List<TileEntity> tileEntityListTick = me.jellysquid.mods.lithium.common.util.collections.HashedList.wrapper(Lists.newArrayList()); // Yatopia
+    public List<TileEntity> tileEntityListTick = me.jellysquid.mods.lithium.common.util.collections.HashedList.wrapper(Lists.newArrayList()); // Yatopia
     protected final List<TileEntity> tileEntityListPending = me.jellysquid.mods.lithium.common.util.collections.HashedList.wrapper(Lists.newArrayList()); // Yatopia
     protected final java.util.Set<TileEntity> tileEntityListUnload = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); // Airplane - use set with faster contains
+    // Yatopia start - lithium: skip ticking block entities that are doing nothing
+    private MaskedTickingBlockEntityList<TileEntity> tileEntityListTick$lithium;
+
+    @Override
+    public void setAwake(TileEntity tileEntity, boolean needsTicking) {
+        this.tileEntityListTick$lithium.setEntryVisible(tileEntity, needsTicking);
+    }
+    // Yatopia end
     public final Thread serverThread;
     private final boolean debugWorld;
     private int d;
@@ -368,6 +379,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki
         this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper
         this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime);
         this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime);
+        this.tileEntityListTick$lithium = new MaskedTickingBlockEntityList<>(this.tileEntityListTick, blockEntity -> ((SleepingBlockEntity) blockEntity).canTickOnSide(false)); // Yatopia
+        this.tileEntityListTick = tileEntityListTick$lithium; // Yatopia
     }
 
     // Paper start
@@ -1032,6 +1045,15 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki
 
     }
 
+    // Yatopia start - lithium: skip ticking block entities that are doing nothing
+    public Iterator<TileEntity> getAwakeBlockEntities(List<TileEntity> list) {
+        if (list == this.tileEntityListTick && list instanceof MaskedTickingBlockEntityList) {
+            return ((MaskedTickingBlockEntityList<TileEntity>) list).filteredIterator();
+        }
+        return list.iterator();
+    }
+    // Yatopia end
+
     public void tickBlockEntities() {
         GameProfilerFiller gameprofilerfiller = this.getMethodProfiler();
 
@@ -1054,11 +1076,19 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki
 
         this.tickingTileEntities = true;
         // Spigot start
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
         // Iterator iterator = this.tileEntityListTick.iterator();
+        Iterator iterator = getAwakeBlockEntities(this.tileEntityListTick);
+
         int tilesThisCycle = 0;
+        while (iterator.hasNext()) {
+            TileEntity tileentity = (TileEntity) iterator.next();
+        /*
         for (tileTickPosition = 0; tileTickPosition < tileEntityListTick.size(); tileTickPosition++) { // Paper - Disable tick limiters
             tileTickPosition = (tileTickPosition < tileEntityListTick.size()) ? tileTickPosition : 0;
             TileEntity tileentity = (TileEntity) this.tileEntityListTick.get(tileTickPosition);
+            */
+        // Yatopia end
             // Spigot start
             if (tileentity == null) {
                 getServer().getLogger().severe("Spigot has detected a null entity and has removed it, preventing a crash");
@@ -1096,8 +1126,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki
                         throwable.printStackTrace();
                         getServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable)));
                         // Paper end
-                        tilesThisCycle--;
-                        this.tileEntityListTick.remove(tileTickPosition--);
+                        iterator.remove(); // Yatopia
                         continue;
                         // Paper end
                         // Spigot start
@@ -1110,8 +1139,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable, NonBlocki
 
             if (tileentity.isRemoved()) {
                 // Spigot start
-                tilesThisCycle--;
-                this.tileEntityListTick.remove(tileTickPosition--);
+                iterator.remove(); // Yatopia
                 // Spigot end
                 //this.tileEntityList.remove(tileentity); // Paper - remove unused list
                 // Paper - prevent double chunk lookups
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java
index 8a049d3de8937a6c8afe178ccd134e2511fb3baf..9b447a9416613f0ccfeb0f81d5b099fcd0417df7 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java
@@ -27,8 +27,9 @@ import org.bukkit.inventory.InventoryHolder;
 import org.spigotmc.CustomTimingsHandler; // Spigot
 import co.aikar.timings.MinecraftTimings; // Paper
 import co.aikar.timings.Timing; // Paper
+import me.jellysquid.mods.lithium.common.world.blockentity.SleepingBlockEntity;
 
-public abstract class TileEntity implements net.minecraft.server.KeyedObject { // Paper
+public abstract class TileEntity implements net.minecraft.server.KeyedObject, SleepingBlockEntity { // Paper // Yatopia
 
     public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper
     // CraftBukkit start - data containers
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeehive.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeehive.java
index d97acd72c30e1051f3e14c3c23b1a6b6e6fa42f0..b11c260908791d7fca711954ae0b715a9bd0634f 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeehive.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeehive.java
@@ -23,6 +23,9 @@ import net.minecraft.world.level.block.BlockBeehive;
 import net.minecraft.world.level.block.BlockCampfire;
 import net.minecraft.world.level.block.BlockFire;
 import net.minecraft.world.level.block.state.IBlockData;
+import me.jellysquid.mods.lithium.common.util.collections.ListeningList;
+import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker;
+import java.util.ArrayList;
 
 public class TileEntityBeehive extends TileEntity implements ITickable {
 
@@ -30,11 +33,25 @@ public class TileEntityBeehive extends TileEntity implements ITickable {
     @Nullable
     public BlockPosition flowerPos = null;
     public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold
+    public boolean doInit = true; // Yatopia
+    public boolean isTicking = true; // Yatopia
+
 
     public TileEntityBeehive() {
         super(TileEntityTypes.BEEHIVE);
     }
 
+    // Yatopia start - lithium: skip ticking block entities that are doing nothing
+    public void checkSleepState() {
+        if (this.world != null ) {
+            if ((this.bees.size() == 0) == this.isTicking) {
+                this.isTicking = !this.isTicking;
+                ((BlockEntitySleepTracker) this.world).setAwake(this, this.isTicking);
+            }
+        }
+    }
+    // Yatopia end
+
     @Override
     public void update() {
         if (this.d()) {
@@ -304,6 +321,12 @@ public class TileEntityBeehive extends TileEntity implements ITickable {
 
     @Override
     public void tick() {
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (this.doInit) {
+            this.doInit = false;
+            this.checkSleepState();
+        }
+        // Yatopia end
         if (this.bees.size() == 0) { return; } // Yatopia - TE optimizations
         if (!this.world.isClientSide) {
             this.y();
@@ -324,6 +347,7 @@ public class TileEntityBeehive extends TileEntity implements ITickable {
     @Override
     public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) {
         super.load(iblockdata, nbttagcompound);
+        this.checkSleepState(); // Yatopia
         this.bees.clear();
         NBTTagList nbttaglist = nbttagcompound.getList("Bees", 10);
 
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java
index 84f9f52c5b632621b509448ac1c760f64de6b062..c4c983b15c28db856be7335e53637a21af2c1b2f 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java
@@ -20,6 +20,7 @@ import net.minecraft.world.entity.player.EntityHuman;
 import net.minecraft.world.level.World;
 import net.minecraft.world.phys.AxisAlignedBB;
 import org.apache.commons.lang3.mutable.MutableInt;
+import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker;
 
 public class TileEntityBell extends TileEntity implements ITickable {
 
@@ -30,6 +31,8 @@ public class TileEntityBell extends TileEntity implements ITickable {
     private List<EntityLiving> h; private List<EntityLiving> getEntitiesAtRing() { return this.h; } // Paper - OBFHELPER
     private boolean i; private boolean getShouldReveal() { return this.i; } // Paper - OBFHELPER
     private int j;
+    public boolean ringing; // Yatopia
+    public boolean resonating; // Yatopia
 
     public TileEntityBell() {
         super(TileEntityTypes.BELL);
@@ -37,6 +40,11 @@ public class TileEntityBell extends TileEntity implements ITickable {
 
     @Override
     public boolean setProperty(int i, int j) {
+       // Yatopia start - lithium: skip ticking block entities that are doing nothing
+       if (!this.ringing && i == 1 && this.world != null) {
+            ((BlockEntitySleepTracker) this.world).setAwake(this, true);
+        }
+        // Yatopia end
         if (i == 1) {
             this.f();
             this.j = 0;
@@ -81,6 +89,11 @@ public class TileEntityBell extends TileEntity implements ITickable {
             }
         }
 
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (!this.ringing && !this.resonating && this.world != null) {
+            ((BlockEntitySleepTracker)this.world).setAwake(this, false);
+        }
+        // Yatopia end
     }
 
     private void d() {
@@ -88,6 +101,11 @@ public class TileEntityBell extends TileEntity implements ITickable {
     }
 
     public void a(EnumDirection enumdirection) {
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (!this.ringing && this.world != null) {
+            ((BlockEntitySleepTracker)this.world).setAwake(this, true);
+        }
+        // Yatopia end
         BlockPosition blockposition = this.getPosition();
 
         this.c = enumdirection;
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBrewingStand.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBrewingStand.java
index 95ca7d9e1eadbe5cc5674ed7352c1ed4d707363d..b5d5f38e520bb5d7912cc86d8ee25858bbd0786b 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBrewingStand.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBrewingStand.java
@@ -34,6 +34,7 @@ import org.bukkit.event.inventory.BrewEvent;
 import org.bukkit.event.inventory.BrewingStandFuelEvent;
 import org.bukkit.inventory.InventoryHolder;
 // CraftBukkit end
+import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker;
 
 public class TileEntityBrewingStand extends TileEntityContainer implements IWorldInventory, ITickable {
 
@@ -50,6 +51,7 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl
     private int lastTick = MinecraftServer.currentTick;
     public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
     private int maxStack = 64;
+    public boolean isTicking = true; // Yatopia
 
     public void onOpen(CraftHumanEntity who) {
         transaction.add(who);
@@ -72,6 +74,17 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl
         return maxStack;
     }
 
+    // Yatopia start - lithium: skip ticking block entities that are doing nothing
+    @Override
+    public void update() {
+        super.update();
+        if (!this.isTicking && this.world != null) {
+            this.isTicking = true;
+            ((BlockEntitySleepTracker)this.world).setAwake(this, true);
+        }
+    }
+    // Yatopia end
+
     public void setMaxStackSize(int size) {
         maxStack = size;
     }
@@ -141,6 +154,12 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl
 
     @Override
     public void tick() {
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (this.brewTime == 0 && this.world != null) {
+            this.isTicking = false;
+            ((BlockEntitySleepTracker)this.world).setAwake(this, false);
+        }
+        // Yatopia end
         ItemStack itemstack = (ItemStack) this.items.get(4);
 
         if (this.fuelLevel <= 0 && itemstack.getItem() == Items.BLAZE_POWDER) {
@@ -280,6 +299,12 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl
     @Override
     public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) {
         super.load(iblockdata, nbttagcompound);
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (!this.isTicking && this.world != null) {
+            this.isTicking = true;
+            ((BlockEntitySleepTracker) this.world).setAwake(this, true);
+        }
+        // Yatopia end
         this.items = NonNullList.a(this.getSize(), ItemStack.b);
         ContainerUtil.b(nbttagcompound, this.items);
         this.brewTime = nbttagcompound.getShort("BrewTime");
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java
index 08759f461ec947c0d5655557f49d8717afee6f00..cdff01d4da417ef1be6238d3faaf464ee7884bd5 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java
@@ -27,12 +27,18 @@ import org.bukkit.craftbukkit.block.CraftBlock;
 import org.bukkit.craftbukkit.inventory.CraftItemStack;
 import org.bukkit.event.block.BlockCookEvent;
 // CraftBukkit end
+import net.minecraft.core.NonNullList;
+import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker;
 
 public class TileEntityCampfire extends TileEntity implements Clearable, ITickable {
 
     private final NonNullList<ItemStack> items;
     public final int[] cookingTimes;
     public final int[] cookingTotalTimes;
+    // Yatopia start - lithium: skip ticking block entities that are doing nothing
+    public boolean isTicking = true;
+    public boolean doInit = true;
+    // Yatopia end
 
     public TileEntityCampfire() {
         super(TileEntityTypes.CAMPFIRE);
@@ -41,8 +47,50 @@ public class TileEntityCampfire extends TileEntity implements Clearable, ITickab
         this.cookingTotalTimes = new int[4];
     }
 
+    // Yatopia start - lithium: skip ticking block entities that are doing nothing
+    @Override
+    public void update() {
+        super.update();
+        this.checkSleepState();
+    }
+
+    @Override
+    public void invalidateBlockCache() {
+        super.invalidateBlockCache();
+        this.checkSleepState();
+    }
+
+    public void checkSleepState() {
+        if (this.world == null) {
+            return;
+        }
+        boolean shouldTick = false;
+        NonNullList<ItemStack> beingCooked = this.items;
+        for (int i = 0; i < beingCooked.size(); i++) {
+            ItemStack stack = beingCooked.get(i);
+            if (!stack.isEmpty()) {
+                if (this.cookingTimes[i] > 0 || this.getBlock().get(BlockCampfire.LIT)) {
+                    shouldTick = true;
+                    break;
+                }
+            }
+        }
+
+        if (shouldTick != this.isTicking) {
+            this.isTicking = shouldTick;
+            ((BlockEntitySleepTracker)this.world).setAwake(this, shouldTick);
+        }
+    }
+    // Yatopia end
+
     @Override
     public void tick() {
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (this.doInit) {
+            this.doInit = false;
+            this.checkSleepState();
+        }
+        // Yatopia end
         boolean flag = (Boolean) this.getBlock().get(BlockCampfire.LIT);
         boolean flag1 = this.world.isClientSide;
 
@@ -149,6 +197,7 @@ public class TileEntityCampfire extends TileEntity implements Clearable, ITickab
     @Override
     public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) {
         super.load(iblockdata, nbttagcompound);
+        this.checkSleepState(); // Yatopia - lithium: skip ticking block entities that are doing nothing
         this.items.clear();
         ContainerUtil.b(nbttagcompound, this.items);
         int[] aint;
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java
index 111f62d0e5b40e945793b8f504f2c035c0884a6a..130ffed3827d3716430414976a0e4f591598586c 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java
@@ -33,14 +33,27 @@ import net.minecraft.world.level.block.Blocks;
 import org.bukkit.craftbukkit.entity.CraftHumanEntity;
 import org.bukkit.entity.HumanEntity;
 // CraftBukkit end
+ import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker; // Yatopia
 
 public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITickable
 
     private NonNullList<ItemStack> items;
     protected float a;
+    private float getAnimationAngle() { return a; } // Yatopia - OBFHELPER
     protected float b;
+    private float getLastAnimationAngle() { return b; } // Yatopia - OBFHELPER
     public int viewingCount;
     private int j;
+    private int ticksOpen = j; // Yatopia - OBFHELPER
+    private int lastTime; // Yatopia
+
+    // Yatopia start - lithium: skip ticking block entities that are doing nothing
+    public void checkWakeUp() {
+        if ((this.viewingCount != 0 || this.getAnimationAngle() != 0.0F || this.getLastAnimationAngle() != 0) && this.world != null) {
+            ((BlockEntitySleepTracker) this.world).setAwake(this, true);
+        }
+    }
+    // Yatopia end
 
     // CraftBukkit start - add fields and methods
     public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
@@ -113,11 +126,27 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic
     }
 
     public void tick() {
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        // noinspection ConstantConditions
+        int time = (int) this.world.getTime();
+        // ticksOpen == 0 implies most likely that this is the first tick. We don't want to update the value then.
+        // overflow case is handled by not going to sleep when this.ticksOpen == 0
+        if (this.ticksOpen != 0) {
+            this.ticksOpen += time - this.lastTime - 1;
+        }
+        this.lastTime = time;
+        // Yatopia end
+
         int i = this.position.getX();
         int j = this.position.getY();
         int k = this.position.getZ();
 
         ++this.j;
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (this.viewingCount == 0 && this.getAnimationAngle() == 0.0F && this.getLastAnimationAngle() == 0 && this.ticksOpen != 0 && this.world != null) {
+            ((BlockEntitySleepTracker) this.world).setAwake(this, false);
+        }
+        // Yatopia end
     }
 
     public void doOpenLogic() {
@@ -223,6 +252,7 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic
 
     @Override
     public boolean setProperty(int i, int j) {
+        this.checkWakeUp(); // Yatopia - lithium: skip ticking block entities that are doing nothing
         if (i == 1) {
             this.viewingCount = j;
             return true;
@@ -254,7 +284,7 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic
             // CraftBukkit end
             this.onOpen();
         }
-
+        this.checkWakeUp(); // Yatopia - lithium: skip ticking block entities that are doing nothing
     }
 
     @Override
@@ -275,6 +305,7 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic
             // CraftBukkit end
             this.onOpen();
         }
+        this.checkWakeUp(); // Yatopia - lithium: skip ticking block entities that are doing nothing
 
     }
 
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnchantTable.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnchantTable.java
index 4b1cb089355b455c6210f2df1af797cc363997cf..0049a675557cbc05e361c12e9fb84a38a3a21160 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnchantTable.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnchantTable.java
@@ -9,6 +9,7 @@ import net.minecraft.util.MathHelper;
 import net.minecraft.world.INamableTileEntity;
 import net.minecraft.world.entity.player.EntityHuman;
 import net.minecraft.world.level.block.state.IBlockData;
+import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker;
 
 public class TileEntityEnchantTable extends TileEntity implements INamableTileEntity, ITickable {
 
@@ -51,6 +52,7 @@ public class TileEntityEnchantTable extends TileEntity implements INamableTileEn
     @Override
     public void tick() {
         if (!org.yatopiamc.yatopia.server.YatopiaConfig.shouldTickEnchantingTables) { return; } // Yatopia - TE optimizations
+        ((BlockEntitySleepTracker) this.world).setAwake(this, false); // Yatopia
         this.j = this.i;
         this.l = this.k;
         EntityHuman entityhuman = this.world.a((double) this.position.getX() + 0.5D, (double) this.position.getY() + 0.5D, (double) this.position.getZ() + 0.5D, 3.0D, false);
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java
index 8a1425f565204fedd1007862b2af9db4c9217461..6083bec6879a625168bd55944d5c21e8151b8189 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java
@@ -53,9 +53,11 @@ import org.bukkit.event.inventory.FurnaceBurnEvent;
 import org.bukkit.event.inventory.FurnaceExtractEvent;
 import org.bukkit.event.inventory.FurnaceSmeltEvent;
 // CraftBukkit end
+import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker;
 
 public abstract class TileEntityFurnace extends TileEntityContainer implements IWorldInventory, RecipeHolder, AutoRecipeOutput, ITickable {
 
+    private boolean isTicking = true; // Yatopia
     private static final int[] g = new int[]{0};
     private static final int[] h = new int[]{2, 1};
     private static final int[] i = new int[]{1};
@@ -197,6 +199,17 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I
     private int maxStack = MAX_STACK;
     public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
 
+    // Yatopia start - lithium: skip ticking block entities that are doing nothing
+    @Override
+    public void update() {
+        super.update();
+        if (!this.isTicking && this.world != null) {
+            this.isTicking = true;
+            ((BlockEntitySleepTracker) this.world).setAwake(this, true);
+        }
+    }
+    // Yatopia end
+
     public List<ItemStack> getContents() {
         return this.items;
     }
@@ -259,6 +272,12 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I
     @Override
     public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) {
         super.load(iblockdata, nbttagcompound);
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (!this.isTicking && this.world != null) {
+            this.isTicking = true;
+            ((BlockEntitySleepTracker)this.world).setAwake(this, true);
+        }
+        // Yatopia end
         this.items = NonNullList.a(this.getSize(), ItemStack.b);
         ContainerUtil.b(nbttagcompound, this.items);
         this.burnTime = nbttagcompound.getShort("BurnTime");
@@ -389,7 +408,12 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I
         if (flag1) {
             this.update();
         }
-
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (!this.isTicking && this.world != null) {
+            this.isTicking = true;
+            ((BlockEntitySleepTracker)this.world).setAwake(this, true);
+        }
+        // Yatopia end
     }
 
     protected boolean canBurn(@Nullable IRecipe<?> irecipe) {
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java
index c1a90eb01689b8b91200ba49d58360d5ea2004c4..8095740f1848443e87fd52c14d19dabb00a3ecd4 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java
@@ -33,6 +33,7 @@ import net.minecraft.world.phys.shapes.VoxelShapes;
 import org.bukkit.craftbukkit.entity.CraftHumanEntity;
 import org.bukkit.entity.HumanEntity;
 // CraftBukkit end
+import me.jellysquid.mods.lithium.common.world.blockentity.BlockEntitySleepTracker;
 
 public class TileEntityShulkerBox extends TileEntityLootable implements IWorldInventory, ITickable {
 
@@ -41,7 +42,9 @@ public class TileEntityShulkerBox extends TileEntityLootable implements IWorldIn
     public int viewingCount;
     private TileEntityShulkerBox.AnimationPhase i;
     private float j;
+    public float animationProgress = j; // Yatopia - OBFHELPER
     private float k;
+    public float prevAnimationProgress = k; // Yatopia - OBFHELPER
     @Nullable
     private EnumColor l;
     private boolean m;
@@ -96,6 +99,11 @@ public class TileEntityShulkerBox extends TileEntityLootable implements IWorldIn
             this.m();
         }
 
+        // Yatopia start - lithium: skip ticking block entities that are doing nothing
+        if (this.getAnimationPhase() == TileEntityShulkerBox.AnimationPhase.CLOSED && this.prevAnimationProgress == 0f && this.animationProgress == 0f && this.world != null) {
+            ((BlockEntitySleepTracker) this.world).setAwake(this, false);
+        }
+        // Yatopia end
     }
 
     protected void h() {
@@ -127,6 +135,7 @@ public class TileEntityShulkerBox extends TileEntityLootable implements IWorldIn
 
     }
 
+    public TileEntityShulkerBox.AnimationPhase getAnimationPhase() {return this.j(); } // Yatopia - OBFHELPER
     public TileEntityShulkerBox.AnimationPhase j() {
         return this.i;
     }
@@ -209,6 +218,7 @@ public class TileEntityShulkerBox extends TileEntityLootable implements IWorldIn
 
     @Override
     public boolean setProperty(int i, int j) {
+        if (this.world != null && i == 1) ((BlockEntitySleepTracker) this.world).setAwake(this, true); // Yatopia
         if (i == 1) {
             this.viewingCount = j;
             if (j == 0) {
