From 34f5d914f670f71fbe5dad1ea26e7ff2af5e95c3 Mon Sep 17 00:00:00 2001
From: modmuss <modmuss50@gmail.com>
Date: Mon, 25 Nov 2024 18:12:59 +0000
Subject: [PATCH] Add RegistryEntryAddedCallback.allEntries (#4235)

* Add RegistryEntryAddedCallback.allEntries

* Pass a RegistryEntry.Reference

* Remove some temp test code

* Add note about recursion.

(cherry picked from commit aa5b2ca19e535fb47029fb360d297b085b9a21f9)
(cherry picked from commit 2758bfbf6642e58959c4b9ed375a7235822338a0)
---
 .../registry/RegistryEntryAddedCallback.java  | 37 +++++++
 .../sync/RegistryEntryAddedCallbackTest.java  | 98 +++++++++++++++++++
 2 files changed, 135 insertions(+)
 create mode 100644 fabric-registry-sync-v0/src/test/java/net/fabricmc/fabric/test/registry/sync/RegistryEntryAddedCallbackTest.java

diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/RegistryEntryAddedCallback.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/RegistryEntryAddedCallback.java
index dc6cf7056..d4f7aba29 100644
--- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/RegistryEntryAddedCallback.java
+++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/RegistryEntryAddedCallback.java
@@ -16,17 +16,54 @@
 
 package net.fabricmc.fabric.api.event.registry;
 
+import java.util.function.Consumer;
+
 import net.minecraft.registry.Registry;
+import net.minecraft.registry.entry.RegistryEntry;
 import net.minecraft.util.Identifier;
 
 import net.fabricmc.fabric.api.event.Event;
 import net.fabricmc.fabric.impl.registry.sync.ListenableRegistry;
 
+/**
+ * An event for when an entry is added to a registry.
+ *
+ * @param <T> the type of the entry within the registry
+ */
 @FunctionalInterface
 public interface RegistryEntryAddedCallback<T> {
+	/**
+	 * Called when a new entry is added to the registry.
+	 *
+	 * @param rawId the raw id of the entry
+	 * @param id the identifier of the entry
+	 * @param object the object that was added
+	 */
 	void onEntryAdded(int rawId, Identifier id, T object);
 
+	/**
+	 * Get the {@link Event} for the {@link RegistryEntryAddedCallback} for the given registry.
+	 *
+	 * @param registry the registry to get the event for
+	 * @return the event
+	 */
 	static <T> Event<RegistryEntryAddedCallback<T>> event(Registry<T> registry) {
 		return ListenableRegistry.get(registry).fabric_getAddObjectEvent();
 	}
+
+	/**
+	 * Register a callback for all present and future entries in the registry.
+	 *
+	 * <p>Note: The callback is recursive and will be invoked for anything registered within the callback itself.
+	 *
+	 * @param registry the registry to listen to
+	 * @param consumer the callback that accepts a {@link RegistryEntry.Reference}
+	 */
+	static <T> void allEntries(Registry<T> registry, Consumer<RegistryEntry.Reference<T>> consumer) {
+		event(registry).register((rawId, id, object) -> consumer.accept(registry.getEntry(id).orElseThrow()));
+		// Call the consumer for all existing entries, after registering the callback.
+		// This way if the callback registers a new entry, it will also be called for that entry.
+		// It is also important to take a copy of the registry with .toList() to avoid concurrent modification exceptions if the callback modifies the registry.
+		registry.streamEntries().toList().forEach(consumer);
+	}
 }
diff --git a/fabric-registry-sync-v0/src/test/java/net/fabricmc/fabric/test/registry/sync/RegistryEntryAddedCallbackTest.java b/fabric-registry-sync-v0/src/test/java/net/fabricmc/fabric/test/registry/sync/RegistryEntryAddedCallbackTest.java
new file mode 100644
index 000000000..16f3bc0e1
--- /dev/null
+++ b/fabric-registry-sync-v0/src/test/java/net/fabricmc/fabric/test/registry/sync/RegistryEntryAddedCallbackTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.test.registry.sync;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import net.minecraft.Bootstrap;
+import net.minecraft.SharedConstants;
+import net.minecraft.registry.Registry;
+import net.minecraft.registry.RegistryKey;
+import net.minecraft.registry.SimpleRegistry;
+import net.minecraft.registry.entry.RegistryEntry;
+import net.minecraft.util.Identifier;
+
+import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
+import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
+
+public class RegistryEntryAddedCallbackTest {
+	@Mock
+	private Consumer<RegistryEntry.Reference<String>> mockConsumer;
+
+	@Captor
+	private ArgumentCaptor<RegistryEntry.Reference<String>> captor;
+
+	@BeforeAll
+	static void beforeAll() {
+		SharedConstants.createGameVersion();
+		Bootstrap.initialize();
+	}
+
+	@BeforeEach
+	void beforeEach() {
+		MockitoAnnotations.openMocks(this);
+	}
+
+	@Test
+	void testEntryAddedCallback() {
+		RegistryKey<Registry<String>> testRegistryKey = RegistryKey.ofRegistry(id(UUID.randomUUID().toString()));
+		SimpleRegistry<String> testRegistry = FabricRegistryBuilder.createSimple(testRegistryKey)
+				.buildAndRegister();
+
+		Registry.register(testRegistry, id("before"), "before");
+		RegistryEntryAddedCallback.allEntries(testRegistry, mockConsumer);
+
+		// Test that the callback can register new entries.
+		RegistryEntryAddedCallback.allEntries(testRegistry, s -> {
+			if (s.value().equals("before")) {
+				Registry.register(testRegistry, id("during"), "during");
+			}
+		});
+
+		Registry.register(testRegistry, id("after"), "after");
+
+		verify(mockConsumer, times(3)).accept(captor.capture());
+
+		List<String> values = captor.getAllValues()
+				.stream()
+				.map(RegistryEntry.Reference::value)
+				.toList();
+
+		assertEquals(3, values.size());
+		assertEquals("before", values.getFirst());
+		assertEquals("during", values.get(1));
+		assertEquals("after", values.get(2));
+	}
+
+	private static Identifier id(String path) {
+		return Identifier.of("registry_sync_test_entry_added_test", path);
+	}
+}