Skip to content
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
33da0f8
Add testable events
Earthcomputer Dec 15, 2025
278ac4c
feat: Debug API
sylv256 Jan 13, 2026
c22b43f
Merge remote-tracking branch 'origin/26.1' into debug-api
sylv256 Jan 14, 2026
b7ec842
docs(EntityDebugSubscriptionRegistry): clarify it's server-side only
sylv256 Jan 14, 2026
9f343f0
chore: apply licenses
sylv256 Jan 14, 2026
2309980
docs: `Constructor` -> `Factory` terminology on factory interfaces
sylv256 Jan 20, 2026
1edea23
Merge remote-tracking branch 'origin/26.1' into debug-api
sylv256 Jan 20, 2026
f0a43db
fix: depend `fabricloader` `>=0.18.4`
sylv256 Jan 23, 2026
9754e49
refactor: merge `MobMixin` & `EntityMixin`
sylv256 Jan 26, 2026
c3ac770
use @apiNote in javadoc and use package-private Mixins
sylv256 Jan 26, 2026
9f77b0b
Merge remote-tracking branch 'origin/debug-api' into debug-api
sylv256 Jan 26, 2026
d3271d8
refactor!: `net.fabricmc.fabric.api.debug.v1.client` -> `….api.client…
sylv256 Jan 26, 2026
77e5364
Merge remote-tracking branch 'origin/26.1' into debug-api
sylv256 Jan 26, 2026
a4ed9e8
Merge branch '26.1' into debug-api
sylv256 Jan 27, 2026
651b2d8
Merge remote-tracking branch 'origin/26.1' into debug-api
sylv256 Jan 29, 2026
d7a70e9
Merge branch 'test-event-scopes' into debug-api
Kilip1000 Mar 29, 2026
dffdb53
Move event scopes to debug api
Kilip1000 Mar 29, 2026
2cc663f
refactor: ServerMobEffectsGameTest now uses EventScopes
Kilip1000 Mar 29, 2026
14c1700
Merge branch '26.1' into debug-api
Kilip1000 Mar 29, 2026
64235c9
spotless apply
Kilip1000 Mar 29, 2026
6abca36
fix: checkstyle
Kilip1000 Mar 29, 2026
21584f0
chore: more descriptive exceptions
Kilip1000 Mar 29, 2026
55d1173
chore: remove unnecessary code
Kilip1000 Mar 29, 2026
6320f81
refactor: use EventScopes more
Kilip1000 Mar 29, 2026
83b205e
safer updating because less hardcoding
Kilip1000 Mar 30, 2026
61092fc
prettify code
Kilip1000 Mar 30, 2026
981c514
chore: javadoc
Kilip1000 Mar 30, 2026
2e6e6f5
chore: fix grammar
Kilip1000 Mar 30, 2026
a9e2332
Move changes to module `fabric-dev-debug-api-v1`
Kilip1000 Mar 30, 2026
542113a
fix wrong classname
Kilip1000 Mar 30, 2026
aed9993
remove redundant file
Kilip1000 Mar 30, 2026
5bf2f56
Merge branch '26.1' into debug-api
sylv256 Mar 30, 2026
7718a8d
refactor: remove EventScope usage, we can PR that later
sylv256 Mar 30, 2026
7b8f853
fix: dependencies
sylv256 Mar 30, 2026
860f2f6
docs: change description to accurately reflect purpose
sylv256 Mar 30, 2026
16cfcf3
fix: don't include dev debug api
sylv256 Mar 30, 2026
0a7f5dd
refactor: move dev debug api to packages matching debug api
sylv256 Mar 30, 2026
452e8b5
docs: javadoc for EventScope
Kilip1000 Mar 30, 2026
d90e87e
docs: clarify listener type parameter doc
sylv256 Mar 30, 2026
60397ac
docs: use @linkplain's and @see, fix docs
sylv256 Mar 30, 2026
6615de7
fix: class name check of TestableEventFactoryImpl
sylv256 Mar 30, 2026
28aa464
fix: move everything to `debug.dev` packages to avoid javadoc errors
sylv256 Mar 30, 2026
32effd1
docs(EventScope): nitpick changes to phrasing and word choice
sylv256 Mar 30, 2026
bddd1d5
test: add tests for `EventScope`
sylv256 Mar 30, 2026
8cd5b48
docs: change description of Debug API to more accurately reflect its …
sylv256 Mar 30, 2026
1fc8725
fix: remove unnecessary dev env check
sylv256 Mar 30, 2026
08382c5
refactor: move merge EventTestingImpl with EventScopeImpl
sylv256 Mar 30, 2026
c5753e1
chore: spotless apply
sylv256 Mar 30, 2026
6e715f3
refactor: `EventScope#registerScoped` -> `#register`
sylv256 Mar 30, 2026
52d19fd
Merge branch '26.1' into debug-api
sylv256 Mar 31, 2026
6ea9b07
remove hacky inheritor verification
Kilip1000 Apr 1, 2026
85b6147
mark api as stable
Kilip1000 Apr 1, 2026
a8e7e0e
Revert "remove hacky inheritor verification"
Kilip1000 Apr 1, 2026
39d745f
fix: remove inheritance check & ServiceLoader jank, use custom FMJ value
sylv256 Apr 1, 2026
b6c31d4
chore: mark API experimental
sylv256 Apr 1, 2026
68e2541
Revert "docs: change description of Debug API to more accurately refl…
sylv256 Apr 1, 2026
371d949
refactor: remove reliance on Debug API, convert to `test-api`
sylv256 Apr 2, 2026
903bb29
Merge branch '26.1.1' into debug-api
sylv256 Apr 2, 2026
c146b32
test: convert gametest to unit test
sylv256 Apr 2, 2026
0f5b19e
Merge branch '26.1.1' into debug-api
sylv256 Apr 9, 2026
8e85fa8
Merge branch '26.1.2' into debug-api
sylv256 Apr 9, 2026
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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ void setupRepositories(RepositoryHandler repositories) {
def devOnlyModules = [
"fabric-client-gametest-api-v1",
"fabric-gametest-api-v1",
"fabric-dev-debug-api-v1"
]

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private EventFactory() { }
* @return The Event instance.
*/
public static <T> Event<T> createArrayBacked(Class<? super T> type, Function<T[], T> invokerFactory) {
return EventFactoryImpl.createArrayBacked(type, invokerFactory);
return EventFactoryImpl.INSTANCE.createArrayBacked(type, invokerFactory);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.impl.base.toposort.NodeSorting;

class ArrayBackedEvent<T> extends Event<T> {
public class ArrayBackedEvent<T> extends Event<T> {
private final Function<T[], T> invokerFactory;
private final Object lock = new Object();
private T[] handlers;
protected final Object lock = new Object();
protected T[] handlers;
/**
* Registered event phases.
*/
Expand All @@ -44,7 +44,7 @@ class ArrayBackedEvent<T> extends Event<T> {
private final List<EventPhaseData<T>> sortedPhases = new ArrayList<>();

@SuppressWarnings("unchecked")
ArrayBackedEvent(Class<? super T> type, Function<T[], T> invokerFactory) {
protected ArrayBackedEvent(Class<? super T> type, Function<T[], T> invokerFactory) {
this.invokerFactory = invokerFactory;
this.handlers = (T[]) Array.newInstance(type, 0);
update();
Expand All @@ -70,7 +70,7 @@ public void register(Identifier phaseIdentifier, T listener) {
}
}

private EventPhaseData<T> getOrCreatePhase(Identifier id, boolean sortIfCreate) {
protected final EventPhaseData<T> getOrCreatePhase(Identifier id, boolean sortIfCreate) {
EventPhaseData<T> phase = phases.get(id);

if (phase == null) {
Expand All @@ -86,7 +86,7 @@ private EventPhaseData<T> getOrCreatePhase(Identifier id, boolean sortIfCreate)
return phase;
}

private void rebuildInvoker(int newLength) {
protected final void rebuildInvoker(int newLength) {
// Rebuild handlers.
if (sortedPhases.size() == 1) {
// Special case with a single phase: use the array of the phase directly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Function;

Expand All @@ -33,18 +34,29 @@

import net.fabricmc.fabric.api.event.Event;

public final class EventFactoryImpl {
public class EventFactoryImpl {
public static final EventFactoryImpl INSTANCE = ServiceLoader.load(EventFactoryImpl.class).findFirst().orElseGet(EventFactoryImpl::new);
private static final Set<ArrayBackedEvent<?>> ARRAY_BACKED_EVENTS
= Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());

private EventFactoryImpl() { }
protected EventFactoryImpl() {
Class<?> thisClass = getClass();

if (thisClass != EventFactoryImpl.class && !thisClass.getName().equals("net.fabricmc.fabric.impl.debug.dev.TestableEventFactoryImpl")) {
throw new IllegalStateException("You are not allowed to create a custom EventFactoryImpl!");
}
}

public static void invalidate() {
ARRAY_BACKED_EVENTS.forEach(ArrayBackedEvent::update);
}

public static <T> Event<T> createArrayBacked(Class<? super T> type, Function<T[], T> invokerFactory) {
ArrayBackedEvent<T> event = new ArrayBackedEvent<>(type, invokerFactory);
protected <T> ArrayBackedEvent<T> doCreateArrayBacked(Class<? super T> type, Function<T[], T> invokerFactory) {
return new ArrayBackedEvent<>(type, invokerFactory);
}

public final <T> Event<T> createArrayBacked(Class<? super T> type, Function<T[], T> invokerFactory) {
ArrayBackedEvent<T> event = doCreateArrayBacked(type, invokerFactory);
ARRAY_BACKED_EVENTS.add(event);
return event;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/**
* Data of an {@link ArrayBackedEvent} phase.
*/
class EventPhaseData<T> extends SortableNode<EventPhaseData<T>> {
public class EventPhaseData<T> extends SortableNode<EventPhaseData<T>> {
final Identifier id;
T[] listeners;

Expand All @@ -42,6 +42,25 @@ void addListener(T listener) {
listeners[oldLength] = listener;
}

public boolean removeListener(T listener) {
int indexToRemove;

for (indexToRemove = listeners.length - 1; indexToRemove >= 0; indexToRemove--) {
if (listeners[indexToRemove] == listener) {
break;
}
}

if (indexToRemove == -1) {
return false;
}

T[] newListeners = Arrays.copyOf(listeners, listeners.length - 1);
System.arraycopy(listeners, indexToRemove + 1, newListeners, indexToRemove, newListeners.length - indexToRemove);
listeners = newListeners;
return true;
}

@Override
protected String getDescription() {
return id.toString();
Expand Down
5 changes: 2 additions & 3 deletions fabric-debug-api-v1/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@
],
"depends": {
"fabricloader": ">=0.18.4",
"fabric-api-base": "*",
"fabric-registry-sync-v0": "*"
"fabric-api-base": "*"
},
"description": "A toolkit for registering and using debug subscriptions and other debug tools Mojang have created.",
"description": "A toolkit for using debug tools Mojang have created and other utilities in tests and development.",
"mixins": [
"fabric-debug-api-v1.mixins.json",
{
Expand Down
10 changes: 10 additions & 0 deletions fabric-dev-debug-api-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version = getSubprojectVersion(project)

moduleDependencies(project, [
'fabric-api-base',
'fabric-debug-api-v1'
])

testDependencies(project, [
'fabric-gametest-api-v1'
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.api.debug.dev.v1;

import java.util.function.Function;

import org.jetbrains.annotations.ApiStatus;

import net.minecraft.resources.Identifier;

import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.impl.debug.dev.EventScopeImpl;

/**
* Represents a wrapper around a short-lived {@link Event}.
* This class implements {@link AutoCloseable} and is intended to be used in a try-with-resources block.
* When closed, the event will be unregistered.
*/
@ApiStatus.NonExtendable
public interface EventScope extends AutoCloseable {
@Override
void close();

/**
* @param event The {@link Event} to be registered in an {@link EventScope}
* @param listener The event listener
* @param <T> is the type parameter for the event's listener
* @return a new {@link EventScope} instance holding the event, its listener and the event phase {@link Event#DEFAULT_PHASE}
*/
static <T> EventScope registerScoped(Event<T> event, T listener) {
return registerScoped(event, Event.DEFAULT_PHASE, listener);
}

/**
* @param event The {@link Event} to be registered in an {@link EventScope}
* @param phase The {@linkplain EventFactory#createWithPhases(Class, Function, Identifier...) event phase}
* @param listener The event listener
* @param <T> is the type parameter for the event's listener
* @return a new {@link EventScope} instance holding the event, its listener and the event phase
* @see EventFactory#createWithPhases(Class, Function, Identifier...)
*/
static <T> EventScope registerScoped(Event<T> event, Identifier phase, T listener) {
return EventScopeImpl.registerScoped(event, phase, listener);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.impl.debug.dev;

import net.minecraft.resources.Identifier;

import net.fabricmc.fabric.api.debug.dev.v1.EventScope;
import net.fabricmc.fabric.api.event.Event;

public class EventScopeImpl<T> implements EventScope {
private final TestableArrayBackedEvent<T> event;
private final Identifier phase;
private final T listener;

public EventScopeImpl(TestableArrayBackedEvent<T> event, Identifier phase, T listener) {
this.event = event;
this.phase = phase;
this.listener = listener;
}

public static <T> EventScope registerScoped(Event<T> event, Identifier phase, T listener) {
if (!(event instanceof TestableArrayBackedEvent<T> testableEvent)) {
throw new IllegalArgumentException("Event is not testable, something has gone very wrong!");
}

event.register(phase, listener);
return new EventScopeImpl<>(testableEvent, phase, listener);
}

@Override
public void close() {
event.unregister(phase, listener);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.impl.debug.dev;

import java.util.Objects;
import java.util.function.Function;

import net.minecraft.resources.Identifier;

import net.fabricmc.fabric.impl.base.event.ArrayBackedEvent;

public class TestableArrayBackedEvent<T> extends ArrayBackedEvent<T> {
TestableArrayBackedEvent(Class<? super T> type, Function<T[], T> invokerFactory) {
super(type, invokerFactory);
}

public void unregister(Identifier phaseIdentifier, T listener) {
Objects.requireNonNull(phaseIdentifier, "Tried to unregister a listener for a null phase!");
Objects.requireNonNull(listener, "Tried to unregister a null listener!");

synchronized (lock) {
if (getOrCreatePhase(phaseIdentifier, false).removeListener(listener)) {
rebuildInvoker(handlers.length - 1);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.impl.debug.dev;

import java.util.function.Function;

import net.fabricmc.fabric.impl.base.event.ArrayBackedEvent;
import net.fabricmc.fabric.impl.base.event.EventFactoryImpl;

public class TestableEventFactoryImpl extends EventFactoryImpl {
@Override
protected <T> ArrayBackedEvent<T> doCreateArrayBacked(Class<? super T> type, Function<T[], T> invokerFactory) {
return new TestableArrayBackedEvent<>(type, invokerFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
net.fabricmc.fabric.impl.debug.dev.TestableEventFactoryImpl
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions fabric-dev-debug-api-v1/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"schemaVersion": 1,
"id": "fabric-dev-debug-api-v1",
"name": "Fabric Dev Debug API (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-dev-debug-api-v1/icon.png",
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.18.4",
"fabric-api-base": "*",
"fabric-debug-api-v1": "*"
},
"description": "An extension of Debug API with features only available in development environments.",
"custom": {
"fabric-api:module-lifecycle": "experimental"
}
}
Loading
Loading