Skip to content

Commit 51f6169

Browse files
committed
Full rewrite and redesign (#69)
Highlights/main concepts and changes: - Major performance improvements, no transforms needed - ListenerList is merged into EventBus. Groups of them are called a BusGroup now - Posting, registration, etc is all done directly on the events you care about - Events can be records or mutable classes and can opt-into various characteristics - Events can support multi-inheritance with an opt-in - Only monitoring events can receiveCancelled now - Cancellation and monitoring state are decoupled from the event objects themselves, allowing for records and other immutable data structures (such as the upcoming Valhalla value classes) - Strong encapsulation with module system enforcement and sealed types, good separation between API spec and implementation to allow freedom for future internal enhancements See the linked PR for more details.
1 parent bb81df7 commit 51f6169

File tree

138 files changed

+4129
-6078
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+4129
-6078
lines changed

.github/workflows/publish.yml

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ permissions:
99

1010
jobs:
1111
build:
12-
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@main
12+
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@v0
1313
with:
14-
java: 17
15-
gradle_tasks: "publish"
14+
java: 21
15+
gradle_tasks: "check publish"
1616
artifact_name: "eventbus"
1717
secrets:
1818
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
1919
PROMOTE_ARTIFACT_WEBHOOK: ${{ secrets.PROMOTE_ARTIFACT_WEBHOOK }}
2020
PROMOTE_ARTIFACT_USERNAME: ${{ secrets.PROMOTE_ARTIFACT_USERNAME }}
2121
PROMOTE_ARTIFACT_PASSWORD: ${{ secrets.PROMOTE_ARTIFACT_PASSWORD }}
2222
MAVEN_USER: ${{ secrets.MAVEN_USER }}
23-
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
23+
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
24+
GRADLE_CACHE_KEY: ${{ secrets.GRADLE_CACHE_KEY }}

Benchmarking.md

-6
This file was deleted.

README.md

+57-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,64 @@
11
# EventBus
22

3-
EventBus is designed to be a simple subscriber-publisher framework. Originally inspired by [Guava], with intentions to be faster by replacing reflection with generated accessor classes. EventBus is intended to be a generic library usable in any project. It has grown larger API/Feature/Complexity/Dependency wise then it was ever intended, and thus will be receiving some redesigns and simplification eventually.
3+
A flexible, high-performance, thread-safe subscriber-publisher framework designed with modern Java in mind.
44

5-
The major focus of EventBus is fast event dispatching. As such any changes should have benchmarks run. See [Benchmarking].
5+
## Overview
6+
The core functionality of EventBus is to provide a simple and efficient way to handle events in a decoupled manner.
67

7-
### Thanks
8-
[![YourKit](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/)
8+
Each event may have one or more buses associated with it, which are responsible for managing listeners and dispatching
9+
instances of the event object to them. To maximise performance, the underlying implementation is tailored on the fly
10+
based on the event's type, characteristics, inheritance chain and the number and type of listeners registered to the bus.
911

10-
YourKit for providing us access to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvment.
12+
## Quickstart guide
13+
First, add the Forge Maven repository and the EventBus dependency to your project:
14+
```gradle
15+
repositories {
16+
maven {
17+
name = "Forge"
18+
url = "https://maven.minecraftforge.net"
19+
}
20+
}
1121
22+
dependencies {
23+
implementation "net.minecraftforge:eventbus:<version>"
24+
}
25+
```
1226

13-
[Guava]: https://guava.dev/releases/snapshot-jre/api/docs/com/google/common/eventbus/EventBus.html
14-
[Benchmarking]: https://github.com/MinecraftForge/EventBus/blob/master/Benchmarking.md
15-
[YourKit Java Profiler]: https://www.yourkit.com/java/profiler/
27+
You can find the list of available versions [here][Versions].
28+
29+
Now you can start using EventBus in your project. Simple usage example:
30+
```java
31+
import net.minecraftforge.eventbus.api.event.RecordEvent;
32+
import net.minecraftforge.eventbus.api.bus.EventBus;
33+
34+
// Define an event and a bus for it
35+
record PlayerLoggedInEvent(String username) implements RecordEvent {
36+
public static final EventBus<PlayerLoggedInEvent> BUS = EventBus.create(PlayerLoggedInEvent.class);
37+
}
38+
39+
// Register an event listener
40+
PlayerLoggedInEvent.BUS.addListener(event -> System.out.println("Player logged in: " + event.username()));
41+
42+
// Post an event to the registered listeners
43+
PlayerLoggedInEvent.BUS.post(new PlayerLoggedInEvent("Paint_Ninja"));
44+
```
45+
46+
Browse the `net.minecraftforge.eventbus.api` package and read the Javadocs for more information. For real-world
47+
examples, check out Forge's extensive use of EventBus [here][Forge usages].
48+
49+
## Nullability
50+
The entirety of EventBus' API is `@NullMarked` and compliant with the [jSpecify specification](https://jspecify.dev/) -
51+
this means that everything is non-null by default unless otherwise specified.
52+
53+
Attempting to pass a `null` value to a method param that isn't explicitly marked as `@Nullable` is an unsupported
54+
operation and won't be considered a breaking change if a future version throws an exception in such cases when it didn't
55+
before.
56+
57+
## Contributing
58+
One of the main goals of EventBus is performance. As such, any changes should be benchmarked with the `jmh` Gradle task
59+
to help ensure that there are no unintended performance regressions. If you are unsure how to do this or would like
60+
to discuss your ideas before submitting a PR, feel free to reach out to us on the [Forge Discord].
61+
62+
[Versions]: https://files.minecraftforge.net/net/minecraftforge/eventbus/
63+
[Forge usages]: https://github.com/MinecraftForge/MinecraftForge/tree/1.21.x/src/main/java/net/minecraftforge/event
64+
[Forge Discord]: https://discord.minecraftforge.net/

build.gradle

+49-32
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
import net.minecraftforge.gradleutils.PomUtils
2+
import net.ltgt.gradle.errorprone.ErrorProneOptions
3+
import net.ltgt.gradle.nullaway.NullAwayOptions
24

35
plugins {
4-
id 'eclipse'
56
id 'java-library'
67
id 'maven-publish'
7-
id 'com.github.ben-manes.versions' version '0.50.0'
8-
id 'org.gradlex.extra-java-module-info' version '1.8'
9-
id 'net.minecraftforge.gradleutils' version '2.3.3'
10-
id 'net.minecraftforge.licenser' version '1.0.1'
8+
id 'org.gradlex.extra-java-module-info' version '1.11'
9+
id 'net.minecraftforge.gradleutils' version '2.4.13'
10+
id 'net.minecraftforge.licenser' version '1.1.1'
11+
12+
// Enforce jSpecify annotations at compile-time
13+
id 'net.ltgt.errorprone' version '4.1.0'
14+
id 'net.ltgt.nullaway' version '2.2.0'
1115
}
1216

1317
group = 'net.minecraftforge'
14-
version = gradleutils.tagOffsetVersion
15-
print("Version: $version")
18+
version = gitversion.version.tagOffset
19+
print "Version: $version"
1620

1721
java {
18-
toolchain.languageVersion = JavaLanguageVersion.of(16)
22+
toolchain.languageVersion = JavaLanguageVersion.of(21)
1923
modularity.inferModulePath = true
2024
withSourcesJar()
2125
}
@@ -26,12 +30,9 @@ repositories {
2630
}
2731

2832
dependencies {
29-
api(libs.typetools)
30-
implementation(libs.log4j.api)
31-
implementation(libs.bundles.asm)
32-
compileOnly(libs.modlauncher)
33-
compileOnly(libs.securemodules)
34-
compileOnly(libs.nulls)
33+
api libs.jspecify.annotations
34+
errorprone libs.errorprone.core
35+
errorprone libs.nullaway
3536
}
3637

3738
extraJavaModuleInfo {
@@ -42,17 +43,37 @@ changelog {
4243
from '1.0.0'
4344
}
4445

45-
jar {
46+
tasks.withType(JavaCompile).configureEach {
47+
// Set up compile-time enforcement of the jSpecify spec via ErrorProne and NullAway
48+
options.errorprone { ErrorProneOptions errorProne ->
49+
errorProne.disableAllChecks = true // Opt-into only the checks we care about
50+
51+
// Enforce the jSpecify spec
52+
errorProne.nullaway { NullAwayOptions nullAway ->
53+
nullAway.jspecifyMode = true
54+
nullaway.onlyNullMarked = true // Only enforce nullability checks on @NullMarked classes and packages
55+
nullAway.error() // Throw a compile error on jSpecify spec violations
56+
}
57+
58+
// Enforce the ErrorProne checks we care about
59+
errorProne.error("FieldCanBeFinal", "MethodCanBeStatic", "LambdaFunctionalInterface")
60+
}
61+
}
62+
63+
tasks.named('jar', Jar) {
4664
manifest {
4765
attributes([
4866
'Specification-Title': 'EventBus',
49-
'Specification-Version': gradleutils.gitInfo.tag,
67+
'Specification-Version': gitversion.version.info.tag,
5068
'Specification-Vendor': 'Forge Development LLC',
5169
'Implementation-Title': 'EventBus',
5270
'Implementation-Version': project.version,
5371
'Implementation-Vendor': 'Forge Development LLC'
54-
] as LinkedHashMap, 'net/minecraftforge/eventbus/service/')
72+
])
5573
}
74+
75+
reproducibleFileOrder = true
76+
preserveFileTimestamps = false
5677
}
5778

5879
license {
@@ -65,17 +86,15 @@ publishing {
6586
from components.java
6687
artifactId = 'eventbus'
6788
pom {
68-
name = 'Event Bus'
89+
name = 'EventBus'
6990
description = 'High performance Event Bus library'
70-
url = 'https://github.com/MinecraftForge/EventBus'
7191

72-
PomUtils.setGitHubDetails(pom, 'EventBus')
92+
gradleutils.pom.gitHubDetails = pom
7393

7494
license PomUtils.Licenses.LGPLv2_1
7595

7696
developers {
77-
developer PomUtils.Developers.LexManos
78-
developer PomUtils.Developers.cpw
97+
developer PomUtils.Developers.Paint_Ninja
7998
}
8099
}
81100
}
@@ -87,18 +106,16 @@ publishing {
87106

88107
allprojects {
89108
ext.VALID_VMS = [
90-
'Adoptium': [16, 17, 18, 19, 20, 21],
91-
'Amazon': [16, 17, 18, 19, 20, 21],
92-
'Azul': (16..21),
93-
'BellSoft': (16..21),
94-
'Graal_VM': [16, 17, 19, 20, 21],
95-
'IBM': [16, 17, 18, 19, 20 ],
96-
'Microsoft': [16, 17, 21],
97-
'Oracle': (16..21),
98-
'SAP': (16..20)
109+
'Adoptium': [21],
110+
'Amazon': [21],
111+
'Azul': (21),
112+
'BellSoft': (21),
113+
'Graal_VM': [21],
114+
'Microsoft': [21],
115+
'Oracle': (21),
99116
]
100117

101118
// Tests are expensive to run all variants, so only run if asked to
102119
if (!project.hasProperty('bulk_tests'))
103-
ext.VALID_VMS = ['Adoptium': [17]]//, 18, 19, 20, 21] ]
120+
ext.VALID_VMS = ['Adoptium': [21]]
104121
}

buildSrc/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ plugins {
44

55
repositories {
66
mavenCentral()
7-
maven { url 'https://jitpack.io' }
7+
maven { url = 'https://jitpack.io' }
88
}
99

10-
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
10+
java.toolchain.languageVersion = JavaLanguageVersion.of(21)
1111

1212
dependencies {
1313
implementation 'com.github.Steppschuh:Java-Markdown-Generator:1.3.2'

buildSrc/src/main/groovy/AggregateTest.groovy

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ abstract class AggregateTest extends DefaultTask {
2424
if (!test.name.startsWith('TEST-') || !test.name.endsWith('.xml'))
2525
continue
2626

27-
def dirName = test.parentFile.name
27+
String dirName = test.parentFile.name
28+
if (!dirName.contains('-')) continue
2829
def (javaName, javaVersion) = dirName.split('-')
2930
javas.computeIfAbsent(javaName, { [] as SortedSet }).add(javaVersion)
3031

@@ -44,7 +45,7 @@ abstract class AggregateTest extends DefaultTask {
4445
}
4546
test.delete()
4647
}
47-
if (java.listFiles().length == 0)
48+
if (java.listFiles().length === 0)
4849
java.deleteDir()
4950
}
5051

eventbus-jmh/build.gradle

+22-26
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repositories {
1111
}
1212

1313
java {
14-
toolchain.languageVersion = JavaLanguageVersion.of(16)
14+
toolchain.languageVersion = JavaLanguageVersion.of(21)
1515
}
1616

1717
license {
@@ -20,19 +20,14 @@ license {
2020
}
2121

2222
dependencies {
23-
compileOnly(libs.nulls)
24-
25-
implementation(rootProject)
26-
implementation(libs.bundles.asm) // Needed by ModLauncher
27-
implementation(libs.junit.api)
28-
implementation(libs.securemodules)
29-
implementation(libs.modlauncher)
30-
implementation(libs.unsafe)
31-
implementation(projects.eventbusTestJar)
23+
implementation rootProject
24+
implementation projects.eventbusTestJar
3225

3326
implementation libs.jmh.core
3427
runtimeOnly libs.jmh.annotationProcessor
3528
annotationProcessor libs.jmh.annotationProcessor
29+
30+
implementation libs.jspecify.annotations
3631
}
3732

3833
extraJavaModuleInfo {
@@ -57,21 +52,19 @@ tasks.register('archiveJfr', ArchiveJfr) {
5752
output = file('build/jmh_profile_results/archive/')
5853
}
5954

60-
tasks.register('jmh') {
61-
dependsOn 'aggregateJmh'
62-
}
55+
tasks.register('jmh')
6356

6457
((Map<String, List<Integer>>) ext.VALID_VMS).forEach { javaVendor, javaVersions ->
6558
for (Integer javaVersion in javaVersions) {
6659
var output = file("build/jmh_results/jmh-${javaVendor}-${javaVersion}.json")
67-
var outputJfr = file("build/jmh_profile_results/last_run/${javaVendor}-${javaVersion}/")
68-
outputJfr.mkdirs()
60+
output.mkdirs()
61+
// var outputJfr = file("build/jmh_profile_results/last_run/${javaVendor}-${javaVersion}/")
62+
// outputJfr.mkdirs()
6963
var task = tasks.register("jmh${javaVendor}${javaVersion}", JavaExec) {
7064
classpath = sourceSets.main.runtimeClasspath
7165
mainModule = 'net.minecraftforge.eventbus.jmh'
7266
mainClass = 'net.minecraftforge.eventbus.benchmarks.Main'
7367
modularity.inferModulePath = true
74-
systemProperty 'jmh.ignoreLock', 'true'
7568
jvmArgs += '-Xmx8g' // Some tests are fast enough that they generate millions of class files, so, give more memory
7669
args = [
7770
'-bm', 'AverageTime', // Benchmark mode. Available modes are: [Throughput, AverageTime, SampleTime, SingleShotTime, All]
@@ -83,9 +76,9 @@ tasks.register('jmh') {
8376
'-f', '1', // Forks per benchmark
8477
'-rf', 'json', // Results File Format
8578

86-
// Todo: Conditionally enable these when given a debug flag. Log a warning if they are enabled
87-
// saying that they should only be used for identifying hotspots and to not trust the
88-
// results of the benchmarks when they are enabled.
79+
// Todo: [EB][Benchmarks] Conditionally enable these when given a debug flag. Log a warning if they
80+
// are enabled saying that they should only be used for identifying hotspots and to not trust
81+
// the results of the benchmarks when they are enabled.
8982
// '-prof', 'stack', // Profiler: Simple and naive Java stack profiler
9083
// '-prof', "jfr:dir=${outputJfr}", // Profiler: Java Flight Recorder profiler
9184

@@ -101,7 +94,7 @@ tasks.register('jmh') {
10194
args += project.property('bench')
10295

10396
doFirst {
104-
outputJfr.deleteDir()
97+
// outputJfr.deleteDir()
10598
if (!output.parentFile.exists())
10699
output.parentFile.mkdirs()
107100
if (output.exists())
@@ -113,13 +106,16 @@ tasks.register('jmh') {
113106
implementation = JvmImplementation.VENDOR_SPECIFIC
114107
}
115108
}
116-
tasks.named('aggregateJmh') {
117-
dependsOn task
118-
inputs.file output
119-
}
120-
tasks.named('archiveJfr') {
109+
tasks.named('jmh') {
121110
dependsOn task
122-
inputs.dir outputJfr
123111
}
112+
// tasks.named('aggregateJmh') {
113+
// dependsOn task
114+
// inputs.file output
115+
// }
116+
// tasks.named('archiveJfr') {
117+
// dependsOn task
118+
// inputs.dir outputJfr
119+
// }
124120
}
125121
}

0 commit comments

Comments
 (0)