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

Split BuildCreativeModeTabContentsEvent internals to operate on two separate sets #1156

Merged
merged 24 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9d0daaf
Split BuildCreativeModeTabContentsEvent to operate on two separate lists
TelepathicGrunt Jun 21, 2024
c925152
Merge branch '1.21.x' into CreativeTabSort
TelepathicGrunt Jun 21, 2024
80f960f
Reuse tab visibility checks
TelepathicGrunt Jun 21, 2024
09e6b51
only update indices of affected entries
TelepathicGrunt Jun 21, 2024
6a6336b
oops
TelepathicGrunt Jun 21, 2024
619082c
Add InsertableLinkedOpenCustomHashSet by Ratatosk
TelepathicGrunt Jun 22, 2024
6e4c947
Merge branch '1.21.x' into CreativeTabSort
TelepathicGrunt Jun 22, 2024
fac39ac
Add and test validations for event
TelepathicGrunt Jun 23, 2024
c292f33
Merge branch 'CreativeTabSort' of https://github.com/TelepathicGrunt/…
TelepathicGrunt Jun 23, 2024
0dee0c3
Merge branch '1.21.x' into CreativeTabSort
TelepathicGrunt Jun 23, 2024
80a3579
Formatter apply
TelepathicGrunt Jun 23, 2024
f36ec1c
Add duplicate entry validation checks
TelepathicGrunt Jun 23, 2024
9add054
two more duplicate validation checks
TelepathicGrunt Jun 23, 2024
4b9dc70
Move strategy constants to their own classes
TelepathicGrunt Jun 23, 2024
4fccefd
run formatter
TelepathicGrunt Jun 23, 2024
baf8eac
Return unmodifiable set
TelepathicGrunt Jun 23, 2024
8433ac2
Merge branch '1.21.x' into CreativeTabSort
Matyrobbrt Jun 24, 2024
c6f9b1a
Add documentation, adjust tests, and cleanup/fix behavior
dhyces Jun 26, 2024
b03ee56
Run formatter
dhyces Jun 26, 2024
b60e159
Merge pull request #1 from dhyces/fix/creative-tab-sort
TelepathicGrunt Jun 26, 2024
3223b8a
Merge branch '1.21.x' into CreativeTabSort
TelepathicGrunt Jun 26, 2024
b25bdfb
Merge branch '1.21.x' into CreativeTabSort
TelepathicGrunt Jul 2, 2024
8ea7833
did some sci suggestions
TelepathicGrunt Jul 6, 2024
7b5a8ab
Merge branch '1.21.x' into CreativeTabSort
TelepathicGrunt Jul 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.common.util;

import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.Pair;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Objects;
import net.neoforged.neoforge.common.util.strategy.BasicStrategy;
import net.neoforged.neoforge.common.util.strategy.IdentityStrategy;

/**
* Special linked hash set that allow changing the order of its entries and is strict to throw if attempting to add an entry that already exists.
* Requires a strategy for the hashing behavior. Use {@link InsertableStrictLinkedHashSet#BASIC} or {@link InsertableStrictLinkedHashSet#IDENTITY} if no special hashing needed.
*/
public class InsertableStrictLinkedHashSet<T> extends AbstractSet<T> {
/**
* A strategy that uses {@link Objects#hashCode(Object)} and {@link Object#equals(Object)}.
*/
public static final Hash.Strategy<? super Object> BASIC = new BasicStrategy();

/**
* A strategy that uses {@link System#identityHashCode(Object)} and {@code a == b} comparisons.
*/
public static final Hash.Strategy<? super Object> IDENTITY = new IdentityStrategy();

private final Hash.Strategy<? super T> strategy;
private final LinkedList<T> backingList = new LinkedList<>();
transient HashMap<Integer, Pair<T, Integer>> map = new HashMap<>();

public InsertableStrictLinkedHashSet(Hash.Strategy<? super T> strategy) {
super();
this.strategy = strategy;
}

@Override
public Iterator<T> iterator() {
return backingList.iterator();
}

@Override
public int size() {
return backingList.size();
}

@Override
public boolean add(T entry) {
int hash = strategy.hashCode(entry);
if (map.containsKey(hash)) {
throw new IllegalArgumentException(entry + " object with hash " + hash + " already exists in set");
}

map.put(hash, Pair.of(entry, backingList.size()));
backingList.add(entry);
return true;
}

public void add(int index, T entry) {
int hash = strategy.hashCode(entry);
if (map.containsKey(hash)) {
throw new IllegalArgumentException(entry + " object with hash " + hash + " already exists in set");
}

backingList.add(index, entry);

// Need to update all existing indices stored in map.
map.clear();
for (int i = 0; i < backingList.size(); i++) {
T curr = backingList.get(i);
map.put(strategy.hashCode(curr), Pair.of(curr, i));
}
TelepathicGrunt marked this conversation as resolved.
Show resolved Hide resolved
}

public void addFirst(T entry) {
int hash = strategy.hashCode(entry);
if (map.containsKey(hash)) {
throw new IllegalArgumentException(entry + " object with hash " + hash + " already exists in set");
}

map.put(hash, Pair.of(entry, backingList.size()));
backingList.addFirst(entry);

// Need to update all existing indices stored in map.
map.clear();
for (int i = 0; i < backingList.size(); i++) {
T curr = backingList.get(i);
map.put(strategy.hashCode(curr), Pair.of(curr, i));
}
}

@Override
public boolean remove(Object o) {
int hash = strategy.hashCode((T) o);
if (!map.containsKey(hash)) {
return false;
}

Pair<T, Integer> removed = map.remove(hash);
backingList.remove((int) removed.right());
return true;
}

public int indexOf(T entry) {
int hash = strategy.hashCode(entry);
if (!map.containsKey(hash)) {
return -1;
}

return map.get(hash).right();
}

public void clear() {
map.clear();
backingList.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.BiPredicate;
import net.neoforged.neoforge.common.util.strategy.BasicStrategy;
import net.neoforged.neoforge.common.util.strategy.IdentityStrategy;
import org.jetbrains.annotations.Nullable;

/**
Expand Down Expand Up @@ -392,28 +394,4 @@ public int hashCode() {
(value == null ? 0 : value.hashCode());
}
}

private static class BasicStrategy implements Strategy<Object> {
@Override
public int hashCode(Object o) {
return Objects.hashCode(o);
}

@Override
public boolean equals(Object a, Object b) {
return Objects.equals(a, b);
}
}

private static class IdentityStrategy implements Strategy<Object> {
@Override
public int hashCode(Object o) {
return System.identityHashCode(o);
}

@Override
public boolean equals(Object a, Object b) {
return a == b;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.common.util.strategy;

import it.unimi.dsi.fastutil.Hash;
import java.util.Objects;

public class BasicStrategy implements Hash.Strategy<Object> {
sciwhiz12 marked this conversation as resolved.
Show resolved Hide resolved
@Override
public int hashCode(Object o) {
return Objects.hashCode(o);
}

@Override
public boolean equals(Object a, Object b) {
return Objects.equals(a, b);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.common.util.strategy;

import it.unimi.dsi.fastutil.Hash;

public class IdentityStrategy implements Hash.Strategy<Object> {
sciwhiz12 marked this conversation as resolved.
Show resolved Hide resolved
@Override
public int hashCode(Object o) {
return System.identityHashCode(o);
}

@Override
public boolean equals(Object a, Object b) {
return a == b;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package net.neoforged.neoforge.common.util.strategy;

import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@

package net.neoforged.neoforge.event;

import java.util.List;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.ItemStack;
import net.neoforged.bus.api.Event;
import net.neoforged.fml.event.IModBusEvent;
import net.neoforged.neoforge.common.util.MutableHashedLinkedMap;
import net.neoforged.neoforge.common.util.InsertableStrictLinkedHashSet;
import org.jetbrains.annotations.ApiStatus;

/**
Expand All @@ -24,15 +25,17 @@
public final class BuildCreativeModeTabContentsEvent extends Event implements IModBusEvent, CreativeModeTab.Output {
private final CreativeModeTab tab;
private final CreativeModeTab.ItemDisplayParameters parameters;
private final MutableHashedLinkedMap<ItemStack, CreativeModeTab.TabVisibility> entries;
private final InsertableStrictLinkedHashSet<ItemStack> parentEntries;
private final InsertableStrictLinkedHashSet<ItemStack> searchEntries;
private final ResourceKey<CreativeModeTab> tabKey;

@ApiStatus.Internal
public BuildCreativeModeTabContentsEvent(CreativeModeTab tab, ResourceKey<CreativeModeTab> tabKey, CreativeModeTab.ItemDisplayParameters parameters, MutableHashedLinkedMap<ItemStack, CreativeModeTab.TabVisibility> entries) {
public BuildCreativeModeTabContentsEvent(CreativeModeTab tab, ResourceKey<CreativeModeTab> tabKey, CreativeModeTab.ItemDisplayParameters parameters, InsertableStrictLinkedHashSet<ItemStack> parentEntries, InsertableStrictLinkedHashSet<ItemStack> searchEntries) {
this.tab = tab;
this.tabKey = tabKey;
this.parameters = parameters;
this.entries = entries;
this.parentEntries = parentEntries;
this.searchEntries = searchEntries;
}

/**
Expand Down Expand Up @@ -61,12 +64,138 @@ public boolean hasPermissions() {
return this.parameters.hasPermissions();
}

public MutableHashedLinkedMap<ItemStack, CreativeModeTab.TabVisibility> getEntries() {
return this.entries;
/**
* The current immutable list of the parent tab entries in the order to be added to the Creative Menu.
* Purely for querying to see what in it. Please use the other event methods for modifications.
*/
public List<ItemStack> getParentEntries() {
return this.parentEntries.stream().toList();
}

/**
* The current immutable list of the search tab entries in the order to be added to the Creative Menu.
* Purely for querying to see what in it. Please use the other event methods for modifications.
*/
public List<ItemStack> getSearchEntries() {
return this.searchEntries.stream().toList();
}
TelepathicGrunt marked this conversation as resolved.
Show resolved Hide resolved

/**
* Inserts the new stack at the end of the given tab at this point in time.
*
* @exception IllegalArgumentException if the new itemstack's count is not 1.
*/
@Override
public void accept(ItemStack stack, CreativeModeTab.TabVisibility visibility) {
getEntries().put(stack, visibility);
public void accept(ItemStack newStack, CreativeModeTab.TabVisibility visibility) {
if (newStack.getCount() != 1)
throw new IllegalArgumentException("The stack count must be 1");

if (visibility == CreativeModeTab.TabVisibility.PARENT_TAB_ONLY) {
TelepathicGrunt marked this conversation as resolved.
Show resolved Hide resolved
parentEntries.add(newStack);
} else if (visibility == CreativeModeTab.TabVisibility.SEARCH_TAB_ONLY) {
searchEntries.add(newStack);
} else {
parentEntries.add(newStack);
searchEntries.add(newStack);
}
}

/**
* Inserts the new entry after the specified existing entry.
*
* @exception UnsupportedOperationException if the existing entry is not found in the tab's lists.
* @exception IllegalArgumentException if the new itemstack's count is not 1.
*/
public void putAfter(ItemStack existingEntry, ItemStack newEntry, CreativeModeTab.TabVisibility visibility) {
TelepathicGrunt marked this conversation as resolved.
Show resolved Hide resolved
if (newEntry.getCount() != 1)
throw new IllegalArgumentException("The stack count must be 1");

if (isParentTab(visibility)) {
insertAfterOperation(existingEntry, newEntry, parentEntries);
}

if (isSearchTab(visibility)) {
insertAfterOperation(existingEntry, newEntry, searchEntries);
}
}

/**
* Inserts the new entry before the specified existing entry.
*
* @exception UnsupportedOperationException if the existing entry is not found in the tab's lists.
* @exception IllegalArgumentException if the new itemstack's count is not 1.
*/
public void putBefore(ItemStack existingEntry, ItemStack newEntry, CreativeModeTab.TabVisibility visibility) {
if (newEntry.getCount() != 1)
throw new IllegalArgumentException("The stack count must be 1");

if (isParentTab(visibility)) {
insertBeforeOperation(existingEntry, newEntry, parentEntries);
}

if (isSearchTab(visibility)) {
insertBeforeOperation(existingEntry, newEntry, searchEntries);
}
}

/**
* Inserts the new entry in the front of the tab's content.
*
* @exception IllegalArgumentException if the new itemstack's count is not 1.
*/
public void putFirst(ItemStack newEntry, CreativeModeTab.TabVisibility visibility) {
if (newEntry.getCount() != 1)
throw new IllegalArgumentException("The stack count must be 1");

if (isParentTab(visibility)) {
parentEntries.addFirst(newEntry);
}

if (isSearchTab(visibility)) {
searchEntries.addFirst(newEntry);
}
TelepathicGrunt marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Removes an entry from the tab's content.
*/
public void remove(ItemStack existingEntry, CreativeModeTab.TabVisibility visibility) {
if (isParentTab(visibility)) {
parentEntries.remove(existingEntry);
}

if (isSearchTab(visibility)) {
searchEntries.remove(existingEntry);
}
}

private void insertAfterOperation(ItemStack existingEntry, ItemStack newEntry, InsertableStrictLinkedHashSet<ItemStack> searchEntries) {
int destinationIndex = searchEntries.indexOf(existingEntry);
if (destinationIndex == -1) {
throw new UnsupportedOperationException("Tried to put " + newEntry.getItem() + " after " + existingEntry.getItem() + " that does not exist in tab");
} else if (destinationIndex + 1 < searchEntries.size()) {
searchEntries.add(destinationIndex + 1, newEntry);
} else {
searchEntries.add(newEntry);
}
}

private void insertBeforeOperation(ItemStack existingEntry, ItemStack newEntry, InsertableStrictLinkedHashSet<ItemStack> parentEntries) {
int destinationIndex = parentEntries.indexOf(existingEntry);
if (destinationIndex == -1) {
throw new UnsupportedOperationException("Tried to put " + newEntry.getItem() + " before " + existingEntry.getItem() + " that does not exist in tab");
} else if (destinationIndex < parentEntries.size()) {
parentEntries.add(destinationIndex, newEntry);
} else {
parentEntries.add(newEntry);
}
}

private static boolean isParentTab(CreativeModeTab.TabVisibility visibility) {
return visibility == CreativeModeTab.TabVisibility.PARENT_TAB_ONLY || visibility == CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS;
}

private static boolean isSearchTab(CreativeModeTab.TabVisibility visibility) {
return visibility == CreativeModeTab.TabVisibility.SEARCH_TAB_ONLY || visibility == CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS;
}
}
Loading
Loading