mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 19:04:49 +00:00
Changed pom to be correct.
Fixed SnowflakeGenerator not making unique ids. Changed naming for event implementation. Automated id getter for events. Added Error-Prone to all modules. Added parents to all modules. Added processors module.
This commit is contained in:
@@ -9,31 +9,16 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
* A thread-safe, distributed unique ID generator following the Snowflake pattern.
|
||||
*
|
||||
* <p>Each generated 64-bit ID encodes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>41-bit timestamp (milliseconds since custom epoch)
|
||||
* <li>10-bit machine identifier
|
||||
* <li>12-bit sequence number for IDs generated in the same millisecond
|
||||
* </ul>
|
||||
*
|
||||
* <p>This implementation ensures:
|
||||
*
|
||||
* <ul>
|
||||
* <li>IDs are unique per machine.
|
||||
* <li>Monotonicity within the same machine.
|
||||
* <li>Safe concurrent generation via synchronized {@link #nextId()}.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Custom epoch is set to {@code 2025-01-01T00:00:00Z}.
|
||||
*
|
||||
* <p>Usage example:
|
||||
*
|
||||
* <pre>{@code
|
||||
* SnowflakeGenerator generator = new SnowflakeGenerator();
|
||||
* long id = generator.nextId();
|
||||
* }</pre>
|
||||
* <p>This static implementation ensures global uniqueness per JVM process
|
||||
* and can be accessed via {@link SnowflakeGenerator#nextId()}.
|
||||
*/
|
||||
public class SnowflakeGenerator {
|
||||
public final class SnowflakeGenerator {
|
||||
|
||||
/** Custom epoch in milliseconds (2025-01-01T00:00:00Z). */
|
||||
private static final long EPOCH = Instant.parse("2025-01-01T00:00:00Z").toEpochMilli();
|
||||
@@ -43,25 +28,26 @@ public class SnowflakeGenerator {
|
||||
private static final long MACHINE_BITS = 10;
|
||||
private static final long SEQUENCE_BITS = 12;
|
||||
|
||||
// Maximum values for each component
|
||||
// Maximum values
|
||||
private static final long MAX_MACHINE_ID = (1L << MACHINE_BITS) - 1;
|
||||
private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
|
||||
private static final long MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) - 1;
|
||||
|
||||
// Bit shifts for composing the ID
|
||||
// Bit shifts
|
||||
private static final long MACHINE_SHIFT = SEQUENCE_BITS;
|
||||
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_BITS;
|
||||
|
||||
/** Unique machine identifier derived from network interfaces (10 bits). */
|
||||
private static final long machineId = SnowflakeGenerator.genMachineId();
|
||||
/** Unique machine identifier derived from MAC addresses. */
|
||||
private static final long MACHINE_ID = genMachineId();
|
||||
|
||||
private final AtomicLong lastTimestamp = new AtomicLong(-1L);
|
||||
private long sequence = 0L;
|
||||
/** State variables (shared across all threads). */
|
||||
private static final AtomicLong LAST_TIMESTAMP = new AtomicLong(-1L);
|
||||
private static long sequence = 0L;
|
||||
|
||||
/**
|
||||
* Generates a 10-bit machine identifier based on MAC addresses of network interfaces. Falls
|
||||
* back to a random value if MAC cannot be determined.
|
||||
*/
|
||||
// Prevent instantiation
|
||||
private SnowflakeGenerator() {}
|
||||
|
||||
/** Generates a 10-bit machine identifier from MAC or random fallback. */
|
||||
private static long genMachineId() {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -77,48 +63,19 @@ public class SnowflakeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing: manually set the last generated timestamp.
|
||||
*
|
||||
* @param l timestamp in milliseconds
|
||||
*/
|
||||
void setTime(long l) {
|
||||
this.lastTimestamp.set(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SnowflakeGenerator. Validates that the machine ID is within allowed range.
|
||||
*
|
||||
* @throws IllegalArgumentException if machine ID is invalid
|
||||
*/
|
||||
public SnowflakeGenerator() {
|
||||
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
|
||||
throw new IllegalArgumentException(
|
||||
"Machine ID must be between 0 and " + MAX_MACHINE_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next unique ID.
|
||||
*
|
||||
* <p>If multiple IDs are generated in the same millisecond, a sequence number is incremented.
|
||||
* If the sequence overflows, waits until the next millisecond.
|
||||
*
|
||||
* @return a unique 64-bit ID
|
||||
* @throws IllegalStateException if clock moves backwards or timestamp exceeds 41-bit limit
|
||||
*/
|
||||
public synchronized long nextId() {
|
||||
/** Returns a globally unique 64-bit Snowflake ID. */
|
||||
public static synchronized long nextId() {
|
||||
long currentTimestamp = timestamp();
|
||||
|
||||
if (currentTimestamp < lastTimestamp.get()) {
|
||||
throw new IllegalStateException("Clock moved backwards. Refusing to generate id.");
|
||||
if (currentTimestamp < LAST_TIMESTAMP.get()) {
|
||||
throw new IllegalStateException("Clock moved backwards. Refusing to generate ID.");
|
||||
}
|
||||
|
||||
if (currentTimestamp > MAX_TIMESTAMP) {
|
||||
throw new IllegalStateException("Timestamp bits overflow, Snowflake expired.");
|
||||
throw new IllegalStateException("Timestamp bits overflow — Snowflake expired.");
|
||||
}
|
||||
|
||||
if (currentTimestamp == lastTimestamp.get()) {
|
||||
if (currentTimestamp == LAST_TIMESTAMP.get()) {
|
||||
sequence = (sequence + 1) & MAX_SEQUENCE;
|
||||
if (sequence == 0) {
|
||||
currentTimestamp = waitNextMillis(currentTimestamp);
|
||||
@@ -127,29 +84,22 @@ public class SnowflakeGenerator {
|
||||
sequence = 0L;
|
||||
}
|
||||
|
||||
lastTimestamp.set(currentTimestamp);
|
||||
LAST_TIMESTAMP.set(currentTimestamp);
|
||||
|
||||
return ((currentTimestamp - EPOCH) << TIMESTAMP_SHIFT)
|
||||
| (machineId << MACHINE_SHIFT)
|
||||
| (MACHINE_ID << MACHINE_SHIFT)
|
||||
| sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the next millisecond if sequence overflows.
|
||||
*
|
||||
* @param lastTimestamp previous timestamp
|
||||
* @return new timestamp
|
||||
*/
|
||||
private long waitNextMillis(long lastTimestamp) {
|
||||
/** Waits until next millisecond if sequence exhausted. */
|
||||
private static long waitNextMillis(long lastTimestamp) {
|
||||
long ts = timestamp();
|
||||
while (ts <= lastTimestamp) {
|
||||
ts = timestamp();
|
||||
}
|
||||
while (ts <= lastTimestamp) ts = timestamp();
|
||||
return ts;
|
||||
}
|
||||
|
||||
/** Returns current system timestamp in milliseconds. */
|
||||
private long timestamp() {
|
||||
/** Returns current timestamp in milliseconds. */
|
||||
private static long timestamp() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.toop.framework.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
|
||||
public @interface TestsOnly {}
|
||||
@@ -29,11 +29,7 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
|
||||
.listen(this::handleStopSound)
|
||||
.listen(this::handleMusicStart)
|
||||
.listen(this::handleVolumeChange)
|
||||
.listen(this::handleFxVolumeChange)
|
||||
.listen(this::handleMusicVolumeChange)
|
||||
.listen(this::handleGetVolume)
|
||||
.listen(this::handleGetFxVolume)
|
||||
.listen(this::handleGetMusicVolume)
|
||||
.listen(AudioEvents.ClickButton.class, _ ->
|
||||
soundEffectManager.play("medium-button-click.wav", false));
|
||||
|
||||
@@ -57,41 +53,15 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
|
||||
}
|
||||
|
||||
private void handleVolumeChange(AudioEvents.ChangeVolume event) {
|
||||
this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeControl.MASTERVOLUME);
|
||||
this.audioVolumeManager.setVolume(event.newVolume() / 100, event.controlType());
|
||||
}
|
||||
|
||||
private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) {
|
||||
this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeControl.FX);
|
||||
}
|
||||
|
||||
private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event) {
|
||||
this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeControl.MUSIC);
|
||||
}
|
||||
|
||||
private void handleGetVolume(AudioEvents.GetCurrentVolume event) {
|
||||
private void handleGetVolume(AudioEvents.GetVolume event) {
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
new AudioEvents.GetCurrentVolumeResponse(
|
||||
audioVolumeManager.getVolume(VolumeControl.MASTERVOLUME),
|
||||
event.snowflakeId()))
|
||||
.asyncPostEvent();
|
||||
}
|
||||
|
||||
private void handleGetFxVolume(AudioEvents.GetCurrentFxVolume event) {
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
new AudioEvents.GetCurrentFxVolumeResponse(
|
||||
audioVolumeManager.getVolume(VolumeControl.FX),
|
||||
event.snowflakeId()))
|
||||
.asyncPostEvent();
|
||||
}
|
||||
|
||||
private void handleGetMusicVolume(AudioEvents.GetCurrentMusicVolume event) {
|
||||
new EventFlow()
|
||||
.addPostEvent(
|
||||
new AudioEvents.GetCurrentMusicVolumeResponse(
|
||||
audioVolumeManager.getVolume(VolumeControl.MUSIC),
|
||||
event.snowflakeId()))
|
||||
new AudioEvents.GetVolumeResponse(
|
||||
audioVolumeManager.getVolume(event.controlType()),
|
||||
event.identifier()))
|
||||
.asyncPostEvent();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.dispatch.interfaces.Dispatcher;
|
||||
import org.toop.framework.dispatch.JavaFXDispatcher;
|
||||
import org.toop.framework.annotations.TestsOnly;
|
||||
import org.toop.annotations.TestsOnly;
|
||||
import org.toop.framework.resource.ResourceManager;
|
||||
import org.toop.framework.resource.resources.BaseResource;
|
||||
import org.toop.framework.resource.types.AudioResource;
|
||||
|
||||
@@ -1,100 +1,32 @@
|
||||
package org.toop.framework.audio.events;
|
||||
|
||||
import java.util.Map;
|
||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
||||
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
|
||||
import org.toop.framework.eventbus.events.EventsBase;
|
||||
import org.toop.framework.audio.VolumeControl;
|
||||
import org.toop.framework.eventbus.events.*;
|
||||
import org.toop.framework.eventbus.events.ResponseToUniqueEvent;
|
||||
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||
|
||||
public class AudioEvents extends EventsBase {
|
||||
public record StopAudioManager() implements EventWithoutSnowflake {}
|
||||
/** Stops the audio manager. */
|
||||
public record StopAudioManager() implements GenericEvent {}
|
||||
|
||||
/** Starts playing a sound. */
|
||||
public record PlayEffect(String fileName, boolean loop) implements EventWithoutSnowflake {}
|
||||
/** Start playing a sound effect. */
|
||||
public record PlayEffect(String fileName, boolean loop) implements GenericEvent {}
|
||||
|
||||
public record StopEffect(String fileName) implements EventWithoutSnowflake {}
|
||||
/** Stop playing a sound effect. */
|
||||
public record StopEffect(String fileName) implements GenericEvent {}
|
||||
|
||||
public record StartBackgroundMusic() implements EventWithoutSnowflake {}
|
||||
/** Start background music. */
|
||||
public record StartBackgroundMusic() implements GenericEvent {}
|
||||
|
||||
public record ChangeVolume(double newVolume) implements EventWithoutSnowflake {}
|
||||
/** Change volume, choose type with {@link VolumeControl}. */
|
||||
public record ChangeVolume(double newVolume, VolumeControl controlType) implements GenericEvent {}
|
||||
|
||||
public record ChangeFxVolume(double newVolume) implements EventWithoutSnowflake {}
|
||||
/** Requests the desired volume by selecting it with {@link VolumeControl}. */
|
||||
public record GetVolume(VolumeControl controlType, long identifier) implements UniqueEvent {}
|
||||
|
||||
public record ChangeMusicVolume(double newVolume) implements EventWithoutSnowflake {}
|
||||
/** Response to GetVolume. */
|
||||
public record GetVolumeResponse(double currentVolume, long identifier) implements ResponseToUniqueEvent {}
|
||||
|
||||
public record GetCurrentVolume(long snowflakeId) implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long eventSnowflake() {
|
||||
return snowflakeId;
|
||||
}
|
||||
}
|
||||
|
||||
public record GetCurrentVolumeResponse(double currentVolume, long snowflakeId)
|
||||
implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long eventSnowflake() {
|
||||
return snowflakeId;
|
||||
}
|
||||
}
|
||||
|
||||
public record GetCurrentFxVolume(long snowflakeId) implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long eventSnowflake() {
|
||||
return this.snowflakeId;
|
||||
}
|
||||
}
|
||||
|
||||
public record GetCurrentMusicVolume(long snowflakeId) implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long eventSnowflake() {
|
||||
return this.snowflakeId;
|
||||
}
|
||||
}
|
||||
|
||||
public record GetCurrentFxVolumeResponse(double currentVolume, long snowflakeId)
|
||||
implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long eventSnowflake() {
|
||||
return this.snowflakeId;
|
||||
}
|
||||
}
|
||||
|
||||
public record GetCurrentMusicVolumeResponse(double currentVolume, long snowflakeId)
|
||||
implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long eventSnowflake() {
|
||||
return this.snowflakeId;
|
||||
}
|
||||
}
|
||||
|
||||
public record ClickButton() implements EventWithoutSnowflake {}
|
||||
/** Plays the predetermined sound for pressing a button. */
|
||||
public record ClickButton() implements GenericEvent {}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import org.toop.framework.SnowflakeGenerator;
|
||||
import org.toop.framework.eventbus.events.EventType;
|
||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
||||
import org.toop.framework.eventbus.events.ResponseToUniqueEvent;
|
||||
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||
|
||||
/**
|
||||
* EventFlow is a utility class for creating, posting, and optionally subscribing to events in a
|
||||
* type-safe and chainable manner. It is designed to work with the {@link GlobalEventBus}.
|
||||
*
|
||||
* <p>This class supports automatic UUID assignment for {@link EventWithSnowflake} events, and
|
||||
* <p>This class supports automatic UUID assignment for {@link UniqueEvent} events, and
|
||||
* allows filtering subscribers so they only respond to events with a specific UUID. All
|
||||
* subscription methods are chainable, and you can configure automatic unsubscription after an event
|
||||
* has been successfully handled.
|
||||
@@ -30,7 +31,7 @@ public class EventFlow {
|
||||
/** Cache of constructor handles for event classes to avoid repeated reflection lookups. */
|
||||
private static final Map<Class<?>, MethodHandle> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/** Automatically assigned UUID for {@link EventWithSnowflake} events. */
|
||||
/** Automatically assigned UUID for {@link UniqueEvent} events. */
|
||||
private long eventSnowflake = -1;
|
||||
|
||||
/** The event instance created by this publisher. */
|
||||
@@ -40,7 +41,7 @@ public class EventFlow {
|
||||
private final List<ListenerHandler> listeners = new ArrayList<>();
|
||||
|
||||
/** Holds the results returned from the subscribed event, if any. */
|
||||
private Map<String, Object> result = null;
|
||||
private Map<String, ?> result = null;
|
||||
|
||||
/** Empty constructor (event must be added via {@link #addPostEvent(Class, Object...)}). */
|
||||
public EventFlow() {}
|
||||
@@ -60,7 +61,7 @@ public class EventFlow {
|
||||
// Keep the old class+args version if needed
|
||||
public <T extends EventType> EventFlow addPostEvent(Class<T> eventClass, Object... args) {
|
||||
try {
|
||||
boolean isUuidEvent = EventWithSnowflake.class.isAssignableFrom(eventClass);
|
||||
boolean isUuidEvent = UniqueEvent.class.isAssignableFrom(eventClass);
|
||||
|
||||
MethodHandle ctorHandle =
|
||||
CONSTRUCTOR_CACHE.computeIfAbsent(
|
||||
@@ -81,7 +82,7 @@ public class EventFlow {
|
||||
int expectedParamCount = ctorHandle.type().parameterCount();
|
||||
|
||||
if (isUuidEvent && args.length < expectedParamCount) {
|
||||
this.eventSnowflake = new SnowflakeGenerator().nextId();
|
||||
this.eventSnowflake = SnowflakeGenerator.nextId();
|
||||
finalArgs = new Object[args.length + 1];
|
||||
System.arraycopy(args, 0, finalArgs, 0, args.length);
|
||||
finalArgs[args.length] = this.eventSnowflake;
|
||||
@@ -100,13 +101,8 @@ public class EventFlow {
|
||||
}
|
||||
}
|
||||
|
||||
// public EventFlow addSnowflake() {
|
||||
// this.eventSnowflake = new SnowflakeGenerator(1).nextId();
|
||||
// return this;
|
||||
// }
|
||||
|
||||
/** Subscribe by ID: only fires if UUID matches this publisher's eventId. */
|
||||
public <TT extends EventWithSnowflake> EventFlow onResponse(
|
||||
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(
|
||||
Class<TT> eventClass, Consumer<TT> action, boolean unsubscribeAfterSuccess) {
|
||||
ListenerHandler[] listenerHolder = new ListenerHandler[1];
|
||||
listenerHolder[0] =
|
||||
@@ -114,7 +110,7 @@ public class EventFlow {
|
||||
GlobalEventBus.subscribe(
|
||||
eventClass,
|
||||
event -> {
|
||||
if (event.eventSnowflake() != this.eventSnowflake) return;
|
||||
if (event.getIdentifier() != this.eventSnowflake) return;
|
||||
|
||||
action.accept(event);
|
||||
|
||||
@@ -130,22 +126,21 @@ public class EventFlow {
|
||||
}
|
||||
|
||||
/** Subscribe by ID: only fires if UUID matches this publisher's eventId. */
|
||||
public <TT extends EventWithSnowflake> EventFlow onResponse(
|
||||
Class<TT> eventClass, Consumer<TT> action) {
|
||||
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Class<TT> eventClass, Consumer<TT> action) {
|
||||
return this.onResponse(eventClass, action, true);
|
||||
}
|
||||
|
||||
/** Subscribe by ID without explicit class. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public <TT extends EventWithSnowflake> EventFlow onResponse(
|
||||
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(
|
||||
Consumer<TT> action, boolean unsubscribeAfterSuccess) {
|
||||
ListenerHandler[] listenerHolder = new ListenerHandler[1];
|
||||
listenerHolder[0] =
|
||||
new ListenerHandler(
|
||||
GlobalEventBus.subscribe(
|
||||
event -> {
|
||||
if (!(event instanceof EventWithSnowflake uuidEvent)) return;
|
||||
if (uuidEvent.eventSnowflake() == this.eventSnowflake) {
|
||||
if (!(event instanceof UniqueEvent uuidEvent)) return;
|
||||
if (uuidEvent.getIdentifier() == this.eventSnowflake) {
|
||||
try {
|
||||
TT typedEvent = (TT) uuidEvent;
|
||||
action.accept(typedEvent);
|
||||
@@ -159,7 +154,7 @@ public class EventFlow {
|
||||
throw new ClassCastException(
|
||||
"Cannot cast "
|
||||
+ event.getClass().getName()
|
||||
+ " to EventWithSnowflake");
|
||||
+ " to UniqueEvent");
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -167,7 +162,7 @@ public class EventFlow {
|
||||
return this;
|
||||
}
|
||||
|
||||
public <TT extends EventWithSnowflake> EventFlow onResponse(Consumer<TT> action) {
|
||||
public <TT extends ResponseToUniqueEvent> EventFlow onResponse(Consumer<TT> action) {
|
||||
return this.onResponse(action, true);
|
||||
}
|
||||
|
||||
@@ -214,7 +209,7 @@ public class EventFlow {
|
||||
throw new ClassCastException(
|
||||
"Cannot cast "
|
||||
+ event.getClass().getName()
|
||||
+ " to EventWithSnowflake");
|
||||
+ " to UniqueEvent");
|
||||
}
|
||||
}));
|
||||
this.listeners.add(listenerHolder[0]);
|
||||
@@ -237,7 +232,13 @@ public class EventFlow {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> getResult() {
|
||||
private void clean() {
|
||||
this.listeners.clear();
|
||||
this.event = null;
|
||||
this.result = null;
|
||||
} // TODO
|
||||
|
||||
public Map<String, ?> getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
import org.toop.framework.eventbus.events.EventType;
|
||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
||||
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||
|
||||
/**
|
||||
* GlobalEventBus backed by the LMAX Disruptor for ultra-low latency, high-throughput event
|
||||
@@ -21,7 +21,7 @@ public final class GlobalEventBus {
|
||||
|
||||
/** Map of event class to Snowflake-ID-specific listeners. */
|
||||
private static final Map<
|
||||
Class<?>, ConcurrentHashMap<Long, Consumer<? extends EventWithSnowflake>>>
|
||||
Class<?>, ConcurrentHashMap<Long, Consumer<? extends UniqueEvent>>>
|
||||
UUID_LISTENERS = new ConcurrentHashMap<>();
|
||||
|
||||
/** Disruptor ring buffer size (must be power of two). */
|
||||
@@ -90,7 +90,7 @@ public final class GlobalEventBus {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public static <T extends EventWithSnowflake> void subscribeById(
|
||||
public static <T extends UniqueEvent> void subscribeById(
|
||||
Class<T> eventClass, long eventId, Consumer<T> listener) {
|
||||
UUID_LISTENERS
|
||||
.computeIfAbsent(eventClass, _ -> new ConcurrentHashMap<>())
|
||||
@@ -101,9 +101,9 @@ public final class GlobalEventBus {
|
||||
LISTENERS.values().forEach(list -> list.remove(listener));
|
||||
}
|
||||
|
||||
public static <T extends EventWithSnowflake> void unsubscribeById(
|
||||
public static <T extends UniqueEvent> void unsubscribeById(
|
||||
Class<T> eventClass, long eventId) {
|
||||
Map<Long, Consumer<? extends EventWithSnowflake>> map = UUID_LISTENERS.get(eventClass);
|
||||
Map<Long, Consumer<? extends UniqueEvent>> map = UUID_LISTENERS.get(eventClass);
|
||||
if (map != null) map.remove(eventId);
|
||||
}
|
||||
|
||||
@@ -152,11 +152,11 @@ public final class GlobalEventBus {
|
||||
}
|
||||
|
||||
// snowflake listeners
|
||||
if (event instanceof EventWithSnowflake snowflakeEvent) {
|
||||
Map<Long, Consumer<? extends EventWithSnowflake>> map = UUID_LISTENERS.get(clazz);
|
||||
if (event instanceof UniqueEvent snowflakeEvent) {
|
||||
Map<Long, Consumer<? extends UniqueEvent>> map = UUID_LISTENERS.get(clazz);
|
||||
if (map != null) {
|
||||
Consumer<EventWithSnowflake> listener =
|
||||
(Consumer<EventWithSnowflake>) map.remove(snowflakeEvent.eventSnowflake());
|
||||
Consumer<UniqueEvent> listener =
|
||||
(Consumer<UniqueEvent>) map.remove(snowflakeEvent.getIdentifier());
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.accept(snowflakeEvent);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface EventWithSnowflake extends EventType {
|
||||
Map<String, Object> result();
|
||||
long eventSnowflake();
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
public interface EventWithoutSnowflake extends EventType {}
|
||||
@@ -1,69 +1,4 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Events that are used in the GlobalEventBus class. */
|
||||
public class EventsBase {
|
||||
|
||||
/**
|
||||
* WIP, DO NOT USE!
|
||||
*
|
||||
* @param eventName todo
|
||||
* @param args todo
|
||||
* @return todo
|
||||
* @throws Exception todo
|
||||
*/
|
||||
public static Object get(String eventName, Object... args) throws Exception {
|
||||
Class<?> clazz = Class.forName("org.toop.framework.eventbus.events.Events$ServerEvents$" + eventName);
|
||||
Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new);
|
||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
||||
return constructor.newInstance(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* WIP, DO NOT USE!
|
||||
*
|
||||
* @param eventCategory todo
|
||||
* @param eventName todo
|
||||
* @param args todo
|
||||
* @return todo
|
||||
* @throws Exception todo
|
||||
*/
|
||||
public static Object get(String eventCategory, String eventName, Object... args)
|
||||
throws Exception {
|
||||
Class<?> clazz =
|
||||
Class.forName("org.toop.framework.eventbus.events.Events$" + eventCategory + "$" + eventName);
|
||||
Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new);
|
||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
||||
return constructor.newInstance(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* WIP, DO NOT USE!
|
||||
*
|
||||
* @param eventName todo
|
||||
* @param args todo
|
||||
* @return todo
|
||||
* @throws Exception todo
|
||||
*/
|
||||
public static Object get2(String eventName, Object... args) throws Exception {
|
||||
// Fully qualified class name
|
||||
String className = "org.toop.server.backend.Events$ServerEvents$" + eventName;
|
||||
|
||||
// Load the class
|
||||
Class<?> clazz = Class.forName(className);
|
||||
|
||||
// Build array of argument types
|
||||
Class<?>[] paramTypes = new Class[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
paramTypes[i] = args[i].getClass();
|
||||
}
|
||||
|
||||
// Get the constructor
|
||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
||||
|
||||
// Create a new instance
|
||||
return constructor.newInstance(args);
|
||||
}
|
||||
}
|
||||
public class EventsBase {}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
public interface GenericEvent extends EventType {}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ResponseToUniqueEvent extends UniqueEvent {
|
||||
default Map<String, Object> result() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
try {
|
||||
for (RecordComponent component : this.getClass().getRecordComponents()) {
|
||||
Object value = component.getAccessor().invoke(this);
|
||||
map.put(component.getName(), value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to build result map via reflection", e);
|
||||
}
|
||||
return Map.copyOf(map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
public interface UniqueEvent extends EventType {
|
||||
default long getIdentifier() {
|
||||
try {
|
||||
var method = this.getClass().getMethod("identifier");
|
||||
return (long) method.invoke(this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("No identifier accessor found", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class NetworkingClientManager {
|
||||
}
|
||||
|
||||
long startClientRequest(String ip, int port) {
|
||||
long connectionId = new SnowflakeGenerator().nextId();
|
||||
long connectionId = SnowflakeGenerator.nextId();
|
||||
try {
|
||||
NetworkingClient client =
|
||||
new NetworkingClient(
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.toop.framework.networking.events;
|
||||
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.toop.framework.eventbus.events.EventWithSnowflake;
|
||||
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
|
||||
|
||||
import org.toop.framework.eventbus.events.GenericEvent;
|
||||
import org.toop.framework.eventbus.events.ResponseToUniqueEvent;
|
||||
import org.toop.framework.eventbus.events.UniqueEvent;
|
||||
import org.toop.framework.eventbus.events.EventsBase;
|
||||
import org.toop.annotations.AutoResponseResult;
|
||||
import org.toop.framework.networking.NetworkingClient;
|
||||
|
||||
/**
|
||||
@@ -15,8 +15,8 @@ import org.toop.framework.networking.NetworkingClient;
|
||||
* org.toop.framework.eventbus.GlobalEventBus}.
|
||||
*
|
||||
* <p>This class defines all the events that can be posted or listened to in the networking
|
||||
* subsystem. Events are separated into those with unique IDs (EventWithSnowflake) and those without
|
||||
* (EventWithoutSnowflake).
|
||||
* subsystem. Events are separated into those with unique IDs (UniqueEvent) and those without
|
||||
* (GenericEvent).
|
||||
*/
|
||||
public class NetworkEvents extends EventsBase {
|
||||
|
||||
@@ -30,86 +30,76 @@ public class NetworkEvents extends EventsBase {
|
||||
* instances.
|
||||
*/
|
||||
public record RequestsAllClients(CompletableFuture<List<NetworkingClient>> future)
|
||||
implements EventWithoutSnowflake {}
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Forces all active client connections to close immediately. */
|
||||
public record ForceCloseAllClients() implements EventWithoutSnowflake {}
|
||||
public record ForceCloseAllClients() implements GenericEvent {}
|
||||
|
||||
/** Response indicating a challenge was cancelled. */
|
||||
public record ChallengeCancelledResponse(long clientId, String challengeId)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record ChallengeCancelledResponse(long clientId, String challengeId) implements GenericEvent {}
|
||||
|
||||
/** Response indicating a challenge was received. */
|
||||
public record ChallengeResponse(
|
||||
long clientId, String challengerName, String challengeId, String gameType)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record ChallengeResponse(long clientId, String challengerName, String challengeId, String gameType)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response containing a list of players for a client. */
|
||||
public record PlayerlistResponse(long clientId, String[] playerlist)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record PlayerlistResponse(long clientId, String[] playerlist) implements GenericEvent {}
|
||||
|
||||
/** Response containing a list of games for a client. */
|
||||
public record GamelistResponse(long clientId, String[] gamelist)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record GamelistResponse(long clientId, String[] gamelist) implements GenericEvent {}
|
||||
|
||||
/** Response indicating a game match information for a client. */
|
||||
public record GameMatchResponse(
|
||||
long clientId, String playerToMove, String gameType, String opponent)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record GameMatchResponse(long clientId, String playerToMove, String gameType, String opponent)
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Response indicating the result of a game. */
|
||||
public record GameResultResponse(long clientId, String condition)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record GameResultResponse(long clientId, String condition) implements GenericEvent {}
|
||||
|
||||
/** Response indicating a game move occurred. */
|
||||
public record GameMoveResponse(long clientId, String player, String move, String details)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record GameMoveResponse(long clientId, String player, String move, String details) implements GenericEvent {}
|
||||
|
||||
/** Response indicating it is the player's turn. */
|
||||
public record YourTurnResponse(long clientId, String message)
|
||||
implements EventWithoutSnowflake {}
|
||||
implements GenericEvent {}
|
||||
|
||||
/** Request to send login credentials for a client. */
|
||||
public record SendLogin(long clientId, String username) implements EventWithoutSnowflake {}
|
||||
public record SendLogin(long clientId, String username) implements GenericEvent {}
|
||||
|
||||
/** Request to log out a client. */
|
||||
public record SendLogout(long clientId) implements EventWithoutSnowflake {}
|
||||
public record SendLogout(long clientId) implements GenericEvent {}
|
||||
|
||||
/** Request to retrieve the player list for a client. */
|
||||
public record SendGetPlayerlist(long clientId) implements EventWithoutSnowflake {}
|
||||
public record SendGetPlayerlist(long clientId) implements GenericEvent {}
|
||||
|
||||
/** Request to retrieve the game list for a client. */
|
||||
public record SendGetGamelist(long clientId) implements EventWithoutSnowflake {}
|
||||
public record SendGetGamelist(long clientId) implements GenericEvent {}
|
||||
|
||||
/** Request to subscribe a client to a game type. */
|
||||
public record SendSubscribe(long clientId, String gameType) implements EventWithoutSnowflake {}
|
||||
public record SendSubscribe(long clientId, String gameType) implements GenericEvent {}
|
||||
|
||||
/** Request to make a move in a game. */
|
||||
public record SendMove(long clientId, short moveNumber) implements EventWithoutSnowflake {}
|
||||
public record SendMove(long clientId, short moveNumber) implements GenericEvent {}
|
||||
|
||||
/** Request to challenge another player. */
|
||||
public record SendChallenge(long clientId, String usernameToChallenge, String gameType)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record SendChallenge(long clientId, String usernameToChallenge, String gameType) implements GenericEvent {}
|
||||
|
||||
/** Request to accept a challenge. */
|
||||
public record SendAcceptChallenge(long clientId, int challengeId)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record SendAcceptChallenge(long clientId, int challengeId) implements GenericEvent {}
|
||||
|
||||
/** Request to forfeit a game. */
|
||||
public record SendForfeit(long clientId) implements EventWithoutSnowflake {}
|
||||
public record SendForfeit(long clientId) implements GenericEvent {}
|
||||
|
||||
/** Request to send a message from a client. */
|
||||
public record SendMessage(long clientId, String message) implements EventWithoutSnowflake {}
|
||||
public record SendMessage(long clientId, String message) implements GenericEvent {}
|
||||
|
||||
/** Request to display help to a client. */
|
||||
public record SendHelp(long clientId) implements EventWithoutSnowflake {}
|
||||
public record SendHelp(long clientId) implements GenericEvent {}
|
||||
|
||||
/** Request to display help for a specific command. */
|
||||
public record SendHelpForCommand(long clientId, String command)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record SendHelpForCommand(long clientId, String command) implements GenericEvent {}
|
||||
|
||||
/** Request to close a specific client connection. */
|
||||
public record CloseClient(long clientId) implements EventWithoutSnowflake {}
|
||||
public record CloseClient(long clientId) implements GenericEvent {}
|
||||
|
||||
/**
|
||||
* Event to start a new client connection.
|
||||
@@ -120,61 +110,19 @@ public class NetworkEvents extends EventsBase {
|
||||
* @param port Server port.
|
||||
* @param eventSnowflake Unique event identifier for correlation.
|
||||
*/
|
||||
public record StartClient(String ip, int port, long eventSnowflake)
|
||||
implements EventWithSnowflake {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Stream.of(this.getClass().getRecordComponents())
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
RecordComponent::getName,
|
||||
rc -> {
|
||||
try {
|
||||
return rc.getAccessor().invoke(this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long eventSnowflake() {
|
||||
return this.eventSnowflake;
|
||||
}
|
||||
}
|
||||
public record StartClient(String ip, int port, long eventSnowflake) implements UniqueEvent {}
|
||||
|
||||
/**
|
||||
* Response confirming a client was started.
|
||||
*
|
||||
* @param clientId The client ID assigned to the new connection.
|
||||
* @param eventSnowflake Event ID used for correlation.
|
||||
* @param identifier Event ID used for correlation.
|
||||
*/
|
||||
public record StartClientResponse(long clientId, long eventSnowflake)
|
||||
implements EventWithSnowflake {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Stream.of(this.getClass().getRecordComponents())
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
RecordComponent::getName,
|
||||
rc -> {
|
||||
try {
|
||||
return rc.getAccessor().invoke(this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long eventSnowflake() {
|
||||
return this.eventSnowflake;
|
||||
}
|
||||
}
|
||||
@AutoResponseResult
|
||||
public record StartClientResponse(long clientId, long identifier) implements ResponseToUniqueEvent {}
|
||||
|
||||
/** Generic server response. */
|
||||
public record ServerResponse(long clientId) implements EventWithoutSnowflake {}
|
||||
public record ServerResponse(long clientId) implements GenericEvent {}
|
||||
|
||||
/**
|
||||
* Request to send a command to a server.
|
||||
@@ -182,10 +130,10 @@ public class NetworkEvents extends EventsBase {
|
||||
* @param clientId The client connection ID.
|
||||
* @param args The command arguments.
|
||||
*/
|
||||
public record SendCommand(long clientId, String... args) implements EventWithoutSnowflake {}
|
||||
public record SendCommand(long clientId, String... args) implements GenericEvent {}
|
||||
|
||||
/** WIP (Not working) Request to reconnect a client to a previous address. */
|
||||
public record Reconnect(long clientId) implements EventWithoutSnowflake {}
|
||||
public record Reconnect(long clientId) implements GenericEvent {}
|
||||
|
||||
/**
|
||||
* Response triggered when a message is received from a server.
|
||||
@@ -193,7 +141,7 @@ public class NetworkEvents extends EventsBase {
|
||||
* @param clientId The connection ID that received the message.
|
||||
* @param message The message content.
|
||||
*/
|
||||
public record ReceivedMessage(long clientId, String message) implements EventWithoutSnowflake {}
|
||||
public record ReceivedMessage(long clientId, String message) implements GenericEvent {}
|
||||
|
||||
/**
|
||||
* Request to change a client connection to a new server.
|
||||
@@ -202,12 +150,11 @@ public class NetworkEvents extends EventsBase {
|
||||
* @param ip The new server IP.
|
||||
* @param port The new server port.
|
||||
*/
|
||||
public record ChangeClientHost(long clientId, String ip, int port)
|
||||
implements EventWithoutSnowflake {}
|
||||
public record ChangeClientHost(long clientId, String ip, int port) implements GenericEvent {}
|
||||
|
||||
/** WIP (Not working) Response indicating that the client could not connect. */
|
||||
public record CouldNotConnect(long clientId) implements EventWithoutSnowflake {}
|
||||
public record CouldNotConnect(long clientId) implements GenericEvent {}
|
||||
|
||||
/** Event indicating a client connection was closed. */
|
||||
public record ClosedConnection(long clientId) implements EventWithoutSnowflake {}
|
||||
public record ClosedConnection(long clientId) implements GenericEvent {}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ public class ResourceMeta<T extends BaseResource> {
|
||||
private final T resource;
|
||||
|
||||
public ResourceMeta(String name, T resource) {
|
||||
this.id = new SnowflakeGenerator().nextId();
|
||||
this.id = SnowflakeGenerator.nextId();
|
||||
this.name = name;
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.toop.framework.resource.events;
|
||||
|
||||
import org.toop.framework.eventbus.events.EventWithoutSnowflake;
|
||||
import org.toop.framework.eventbus.events.GenericEvent;
|
||||
|
||||
public class AssetLoaderEvents {
|
||||
public record LoadingProgressUpdate(int hasLoadedAmount, int isLoadingAmount)
|
||||
implements EventWithoutSnowflake {}
|
||||
implements GenericEvent {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user