Skip to content

Commit

Permalink
Add RegistryEntryAddedCallback.allEntries (#4235)
Browse files Browse the repository at this point in the history
* Add RegistryEntryAddedCallback.allEntries

* Pass a RegistryEntry.Reference

* Remove some temp test code

* Add note about recursion.
  • Loading branch information
modmuss50 authored Nov 25, 2024
1 parent 6383078 commit aa5b2ca
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit aa5b2ca

Please sign in to comment.