Система событий

This commit is contained in:
RoyceDa
2026-02-02 22:38:46 +02:00
parent bbc83c7d39
commit bb01add104
13 changed files with 338 additions and 13 deletions

View File

@@ -22,7 +22,7 @@ public class Main {
/**
* Запуск сервера на порту 8881
*/
Server server = new Server(settings, manager, Configuration.class);
Server server = new Server(settings, manager);
server.start();
}
}

View File

@@ -0,0 +1,17 @@
package com.rosetta.im.event;
public interface Cancelable {
/**
* Отменено ли событие
* @return true, если событие отменено
*/
boolean isCanceled();
/**
* Установить отмену события
* @param canceled true, если событие должно быть отменено
*/
void setCanceled(boolean canceled);
}

View File

@@ -0,0 +1,18 @@
package com.rosetta.im.event;
public class Event {
private String name;
public Event() {}
public String getEventName() {
if(this.name == null) {
this.name = this.getClass().getSimpleName();
}
return this.name;
}
}

View File

@@ -0,0 +1,13 @@
package com.rosetta.im.event;
public class EventException extends Exception {
public EventException(String message) {
super(message);
}
public EventException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,12 @@
package com.rosetta.im.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventHandler {
}

View File

@@ -0,0 +1,120 @@
package com.rosetta.im.event;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Менеджер событий
*/
public class EventManager {
private Set<Listener> listeners;
public EventManager() {
this.listeners = new HashSet<>();
}
/**
* Регистрация слушателя событий
* @param listener Слушатель событий
*/
public void registerListener(Listener listener) {
this.listeners.add(listener);
}
/**
* Удаление слушателя событий
* @param listener Слушатель событий
*/
public void unregisterListener(Listener listener) {
this.listeners.remove(listener);
}
/**
* Получить все зарегистрированные слушатели событий
* @return Множество слушателей событий
*/
public Set<Listener> getListeners() {
return this.listeners;
}
/**
* Вызывает событие и обрабатывает исключения
* @param event Событие для вызова
* @return true, если событие отменено
*/
public boolean callEvent(Event event) {
try {
return fireEvent(event);
} catch (EventException e) {
e.printStackTrace();
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Запускает событие, уведомляя всех зарегистрированных слушателей, возвращает true если событие отменено
* @param event Событие для запуска
* @return true, если событие отменено
* @throws EventException Ошибка при обработке события
* @throws Exception Общая ошибка при вызове метода
*/
private boolean fireEvent(Event event) throws EventException, Exception {
for(Listener listener : this.listeners) {
/**
* Получаем все методы в Listener с аннотацией @EventHandler
*/
List<Method> methods = getMethodsWithAnnotation(listener.getClass(), EventHandler.class);
for(Method method : methods) {
/**
* Проверяем, что метод принимает один параметр того же типа, что и событие
*/
if(method.getParameterCount() == 1
&& method.getParameterTypes()[0].isAssignableFrom(event.getClass())) {
/**
* Если параметры совпадают и они одного типа - вызываем событие
*/
method.setAccessible(true);
method.invoke(listener, event);
/**
* Если событие отменяемое - проверяем его статус
*/
if(event instanceof Cancelable) {
Cancelable cancelableEvent = (Cancelable) event;
if(cancelableEvent.isCanceled()) {
return true;
}
}
continue;
}
/**
* Если метод обрабатывающий событие
* реализован неправильно - выбрасываем исключение
*/
throw new EventException("Invalid event params");
}
}
return false;
}
private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotation) {
List<Method> methods = new ArrayList<>();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(annotation)) {
methods.add(method);
}
}
return methods;
}
}

View File

@@ -0,0 +1,25 @@
package com.rosetta.im.event;
/**
* Приоритет события
* Указывает на то, в каком порядке обработаются два одинаковых события
*/
public enum EventPriority {
LOW(0),
MEDIUM(1),
HIGH(2);
private final int priority;
EventPriority(int priority) {
this.priority = priority;
}
/**
* Получить приоритет
* @return Приоритет события
*/
public int getPriority() {
return priority;
}
}

View File

@@ -0,0 +1,10 @@
package com.rosetta.im.event;
/**
* Слушатель событий
*/
public interface Listener {
/**
* Пустой интерфейс для обозначения слушателя событий
*/
}

View File

@@ -0,0 +1,3 @@
package io.orprotocol;
public class Context extends Object {}

View File

@@ -20,23 +20,56 @@ public class Server extends WebSocketServer {
private PacketManager packetManager;
private Settings settings;
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private Object attachment;
private Context context;
private ServerListener listener;
public Server(Settings settings, PacketManager packetManager, Object attachment) {
/**
* Конструктор сервера
* @param settings базовые настройки серверера
* @param packetManager менеджер пакетов (обработчиков и зарегистрированных пакетов)
*/
public Server(Settings settings, PacketManager packetManager) {
super(new InetSocketAddress(settings.port));
this.settings = settings;
this.packetManager = packetManager;
this.attachment = attachment;
}
/**
* Конструктор сервера с объектом прикрепления
* @param settings базовые настройки серверера
* @param packetManager менеджер пакетов (обработчиков и зарегистрированных пакетов)
* @param context вложение которое будет передаваться всем серрверным обработчикам пакетов,
* может быть использовано для передачи контекста приложения
*/
public Server(Settings settings, PacketManager packetManager, Context context) {
super(new InetSocketAddress(settings.port));
this.settings = settings;
this.packetManager = packetManager;
this.context = context;
}
public Server(Settings settings, PacketManager packetManager, Context context, ServerListener listener) {
super(new InetSocketAddress(settings.port));
this.settings = settings;
this.packetManager = packetManager;
this.context = context;
this.listener = listener;
}
@Override
public void onClose(WebSocket arg0, int arg1, String arg2, boolean arg3) {
public void onClose(WebSocket socket, int arg1, String arg2, boolean arg3) {
if(this.listener == null){
return;
}
this.listener.onClientDisconnect(this, socket.getAttachment());
}
@Override
public void onError(WebSocket arg0, Exception arg1) {
if(this.listener == null){
return;
}
this.listener.onError(this, arg1);
}
@Override
@@ -47,6 +80,12 @@ public class Server extends WebSocketServer {
*/
Client client = socket.getAttachment();
client.updateHeartbeat();
if(this.listener != null) {
/**
* Сообщение от клиента, но пакет не сформирован, передаем null
*/
this.listener.onPacketReceived(this, client, null);
}
}
@Override
@@ -90,7 +129,13 @@ public class Server extends WebSocketServer {
Class<? extends PacketExecutor> executorClass = this.packetManager.getExecutors().get(packetId);
PacketExecutor executor = executorClass.getConstructor().newInstance();
executor.settings = this.settings;
executor.attachment = this.attachment;
executor.context = this.context;
if(listener != null && listener.onPacketReceived(this, client, packet)) {
/**
* Если слушатель сервера вернул true, пакет не обрабатываем.
*/
return;
}
executor.onPacketReceived(packet, client);
} catch (Exception e) {
System.out.println("Error while processing packet " + packetClass.getName());
@@ -107,6 +152,13 @@ public class Server extends WebSocketServer {
*/
Client client = new Client(socket, this.settings.heartbeatInterval);
socket.setAttachment(client);
if(this.listener == null){
return;
}
if(this.listener.onClientConnect(this, client)) {
client.disconnect(ServerFailures.SERVER_NOT_ACCEPT_CLIENT);
return;
}
}
@Override
@@ -118,6 +170,10 @@ public class Server extends WebSocketServer {
int port = this.getPort();
System.out.println("\u001B[32mServer started at x.x.x.x:" + port + "\u001B[0m");
if(this.listener == null){
return;
}
this.listener.onServerStart(this);
}
/**

View File

@@ -24,6 +24,11 @@ public enum ServerFailures implements BaseFailures {
* Код ошибки, указывающий на тайм-аут бездействия.
*/
INACTIVITY_TIMEOUT(3004),
/**
* Сервер по какой-то причине не принимает подключение клиента
* Выбрасывается в методе onClientConnect при отмене события например защитой
*/
SERVER_NOT_ACCEPT_CLIENT(3005),
/**
* Код ошибки, указывающий на неизвестный тип пакета.
*/

View File

@@ -0,0 +1,45 @@
package io.orprotocol;
import io.orprotocol.client.Client;
import io.orprotocol.packet.Packet;
public interface ServerListener {
/**
* Сервер запущен
* @param server
*/
void onServerStart(Server server);
/**
* Сервер остановлен
* @param server
*/
void onServerStop(Server server);
/**
* Клиент подключился
* @param server
* @param client
* @return если возвращено true значит клиент не будет подключен к серверу выбросится
* ошибка SERVER_NOT_ACCEPT_CLIENT
*/
boolean onClientConnect(Server server, Client client);
/**
* Клиент отключился
* @param server
* @param client
*/
void onClientDisconnect(Server server, Client client);
/**
* Произошла ошибка сервера
* @param server
* @param exception
*/
void onError(Server server, Exception exception);
/**
* Пакет получен от клиента
* @param server
* @param client
* @param packet
* @return если возвращено true значит пакет не будет обработан дальше
*/
boolean onPacketReceived(Server server, Client client, Packet packet);
}

View File

@@ -1,5 +1,6 @@
package io.orprotocol.packet;
import io.orprotocol.Context;
import io.orprotocol.Settings;
import io.orprotocol.client.Client;
@@ -8,7 +9,7 @@ import io.orprotocol.client.Client;
*/
public abstract class PacketExecutor {
public Settings settings;
public Object attachment;
public Context context;
/**
* Настройки сервера.
@@ -19,11 +20,11 @@ public abstract class PacketExecutor {
}
/**
* Вложенный обьект, который был передан при создании сервера.
* @return вложенный обьект
* Контекст приложения переданный при создании сервера.
* @return контекст
*/
public Object getAttachment() {
return attachment;
public Context getContext() {
return context;
}
/**