Compare commits
14 Commits
8e5a421753
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e073d5161b | |||
| 0d916b56f2 | |||
| 07f96fe511 | |||
| 44ec7317a1 | |||
| 000845d514 | |||
| 05b0c3ed19 | |||
| 0061deb8b0 | |||
| 9e74471cfa | |||
| 287ff9c798 | |||
| 2872e0d98a | |||
| 705812c754 | |||
| 8b3216f416 | |||
| 7021b7a401 | |||
| c0409a612e |
@@ -7,14 +7,14 @@ Testing some (potentially stupid 😅) ideas for a generic event system implment
|
|||||||
|
|
||||||
- [x] ~~Implement system initialisation~~
|
- [x] ~~Implement system initialisation~~
|
||||||
- [x] ~~Implement system deinitialisation~~
|
- [x] ~~Implement system deinitialisation~~
|
||||||
- [ ] Implement registering new events
|
- [x] ~~Implement registering new events~~
|
||||||
- [x] ~~Re-use a free slot if one exists~~
|
- [x] ~~Re-use a free slot if one exists~~
|
||||||
- [x] ~~Register a event new when not out of capacity and no free events exist~~
|
- [x] ~~Register a event new when not out of capacity and no free events exist~~
|
||||||
- [ ] Re-allocate to add more events when out of capacity
|
- [x] ~~Re-allocate to add more events when out of capacity~~
|
||||||
- [x] ~~Implement removing a previously registered event~~
|
- [x] ~~Implement removing a previously registered event~~
|
||||||
- [ ] Implement adding new event listeners
|
- [x] ~~Implement adding new event listeners~~
|
||||||
- [x] ~~Re-use a free slot if one exists~~
|
- [x] ~~Re-use a free slot if one exists~~
|
||||||
- [x] ~~Register a new listeners when not out of capacity and no free listeners exist~~
|
- [x] ~~Register a new listeners when not out of capacity and no free listeners exist~~
|
||||||
- [ ] Re-allocate to add more listeners when out of capacity
|
- [x] ~~Re-allocate to add more listeners when out of capacity~~
|
||||||
- [x] ~~Implement removing an existing listener~~
|
- [x] ~~Implement removing an existing listener~~
|
||||||
- [x] ~~Implement emitting an event~~
|
- [x] ~~Implement emitting an event~~
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <time.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
#define MAGIC 0x4556535953
|
#define MAGIC 0x4556535953
|
||||||
@@ -17,9 +18,13 @@
|
|||||||
#define IS_INVALID_EVENT(EV) (EV.id == 0)
|
#define IS_INVALID_EVENT(EV) (EV.id == 0)
|
||||||
#define IS_INVALID_LISTENER(LS) (LS.id == 0 || IS_INVALID_EVENT(LS.event))
|
#define IS_INVALID_LISTENER(LS) (LS.id == 0 || IS_INVALID_EVENT(LS.event))
|
||||||
|
|
||||||
|
#define CB_ARR_SIZE(CAPACITY) ((CAPACITY + 1) * sizeof(EventCallback))
|
||||||
|
#define FREE_CB_ARR_SIZE(CAPACITY) (CAPACITY * sizeof(u64))
|
||||||
|
|
||||||
typedef struct es_internal ESInternal;
|
typedef struct es_internal ESInternal;
|
||||||
struct es_internal {
|
struct es_internal {
|
||||||
u64 magic;
|
u64 magic;
|
||||||
|
u64 id;
|
||||||
u64 alloc_size;
|
u64 alloc_size;
|
||||||
u64 event_capacity;
|
u64 event_capacity;
|
||||||
u64 event_count;
|
u64 event_count;
|
||||||
@@ -49,10 +54,20 @@ internal void *alloc(u64 size);
|
|||||||
internal ESInternal *get_event_system_internal(EventSystem event_system);
|
internal ESInternal *get_event_system_internal(EventSystem event_system);
|
||||||
internal EventSlot *get_event_slots_array(const ESInternal *es);
|
internal EventSlot *get_event_slots_array(const ESInternal *es);
|
||||||
internal u64 *get_free_array(const ESInternal *es);
|
internal u64 *get_free_array(const ESInternal *es);
|
||||||
|
internal void reallocate_system(EventSystem *event_system, ESInternal *es);
|
||||||
|
internal void copy_event_data(const ESInternal *src, ESInternal *dst);
|
||||||
internal EventInternal *get_event(const ESInternal *es, Event event);
|
internal EventInternal *get_event(const ESInternal *es, Event event);
|
||||||
internal EventInternal *get_event_from_offset(const ESInternal *es, u64 offset);
|
internal EventInternal *get_event_from_offset(const ESInternal *es, u64 offset);
|
||||||
|
internal u64 get_largest_capacity(const ESInternal *es);
|
||||||
|
|
||||||
|
internal bool seeded = false;
|
||||||
|
|
||||||
EventSystem es_init(u64 initial_event_capacity, u64 initial_listeners_capacity) {
|
EventSystem es_init(u64 initial_event_capacity, u64 initial_listeners_capacity) {
|
||||||
|
if (!seeded) {
|
||||||
|
srand(time(0));
|
||||||
|
seeded = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Double event capacity to reduce chances of reallocation
|
// Double event capacity to reduce chances of reallocation
|
||||||
u64 event_capacity = initial_event_capacity * 2;
|
u64 event_capacity = initial_event_capacity * 2;
|
||||||
// Double listener capacity to reduce chances of reallocation
|
// Double listener capacity to reduce chances of reallocation
|
||||||
@@ -62,9 +77,9 @@ EventSystem es_init(u64 initial_event_capacity, u64 initial_listeners_capacity)
|
|||||||
u64 event_offsets = (event_capacity + 1) * sizeof(EventSlot);
|
u64 event_offsets = (event_capacity + 1) * sizeof(EventSlot);
|
||||||
u64 free_slots = event_capacity * sizeof(u64);
|
u64 free_slots = event_capacity * sizeof(u64);
|
||||||
u64 events = event_capacity * sizeof(EventInternal);
|
u64 events = event_capacity * sizeof(EventInternal);
|
||||||
// Increase listener capacity by one to account for 0th EventListener being invalid
|
// Increases listener capacity by one to account for 0th EventListener being invalid
|
||||||
u64 event_callbacks = (callback_capacity + 1) * sizeof(EventCallback *);
|
u64 event_callbacks = CB_ARR_SIZE(callback_capacity);
|
||||||
u64 event_free = callback_capacity * sizeof(u64 *);
|
u64 event_free = FREE_CB_ARR_SIZE(callback_capacity);
|
||||||
u64 callbacks = event_capacity * event_callbacks;
|
u64 callbacks = event_capacity * event_callbacks;
|
||||||
u64 free_callbacks = event_capacity * event_free;
|
u64 free_callbacks = event_capacity * event_free;
|
||||||
u64 alloc_size = system + event_offsets + free_slots + events + callbacks + free_callbacks;
|
u64 alloc_size = system + event_offsets + free_slots + events + callbacks + free_callbacks;
|
||||||
@@ -87,6 +102,7 @@ EventSystem es_init(u64 initial_event_capacity, u64 initial_listeners_capacity)
|
|||||||
ESInternal *event_system = (ESInternal *)buffer;
|
ESInternal *event_system = (ESInternal *)buffer;
|
||||||
|
|
||||||
event_system->magic = MAGIC;
|
event_system->magic = MAGIC;
|
||||||
|
event_system->id = (u64)time(0) + (u64)rand();
|
||||||
event_system->alloc_size = alloc_size;
|
event_system->alloc_size = alloc_size;
|
||||||
event_system->event_capacity = event_capacity;
|
event_system->event_capacity = event_capacity;
|
||||||
event_system->event_count = 0;
|
event_system->event_count = 0;
|
||||||
@@ -143,8 +159,6 @@ Event es_register_event(EventSystem *event_system) {
|
|||||||
goto RETURN_EVENT;
|
goto RETURN_EVENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.system = *event_system;
|
|
||||||
|
|
||||||
EventSlot *event_slots_array = get_event_slots_array(es);
|
EventSlot *event_slots_array = get_event_slots_array(es);
|
||||||
|
|
||||||
if (es->free_count > 0) {
|
if (es->free_count > 0) {
|
||||||
@@ -159,33 +173,38 @@ Event es_register_event(EventSystem *event_system) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (es->event_count >= es->event_capacity) {
|
if (es->event_count >= es->event_capacity) {
|
||||||
// TODO (Abdelrahman): Handle reallocating when out of events
|
reallocate_system(event_system, es);
|
||||||
goto RETURN_EVENT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.id = ++(es->event_count);
|
event.id = ++(es->event_count);
|
||||||
|
event.system = es->id;
|
||||||
|
|
||||||
event_slots_array[event.id].registered = true;
|
event_slots_array[event.id].registered = true;
|
||||||
|
|
||||||
RETURN_EVENT:
|
RETURN_EVENT:
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
void es_deregister_event(EventSystem *event_system, Event *event) {
|
void es_deregister_event(EventSystem event_system, Event *event) {
|
||||||
if (!event_system || IS_INVALID_SYSTEM((*event_system)) || IS_INVALID_EVENT((*event)) || event_system->id != event->system.id) {
|
if (IS_INVALID_SYSTEM(event_system) || IS_INVALID_EVENT((*event))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESInternal *es = get_event_system_internal(*event_system);
|
ESInternal *es = get_event_system_internal(event_system);
|
||||||
if (!VALID_MAGIC(es) || event->id > es->event_count) {
|
if (!VALID_MAGIC(es) || event->system != es->id || event->id > es->event_count) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EventSlot *event_slots_array = get_event_slots_array(es);
|
EventSlot *event_slots_array = get_event_slots_array(es);
|
||||||
u64 *free_array = get_free_array(es);
|
u64 *free_array = get_free_array(es);
|
||||||
|
EventInternal *ev = get_event(es, *event);
|
||||||
|
|
||||||
event_slots_array[event->id].registered = false;
|
event_slots_array[event->id].registered = false;
|
||||||
free_array[(es->free_count)++] = event->id;
|
free_array[(es->free_count)++] = event->id;
|
||||||
|
|
||||||
|
ev->count = 0;
|
||||||
|
ev->free_count = 0;
|
||||||
|
|
||||||
if (event->id == es->event_count) {
|
if (event->id == es->event_count) {
|
||||||
es->event_count -= 1;
|
es->event_count -= 1;
|
||||||
}
|
}
|
||||||
@@ -193,15 +212,15 @@ void es_deregister_event(EventSystem *event_system, Event *event) {
|
|||||||
event->id = 0;
|
event->id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
EventListener es_add_event_listener(EventSystem event_system, Event event, EventCallback callback) {
|
EventListener es_add_event_listener(EventSystem *event_system, Event event, EventCallback callback) {
|
||||||
EventListener listener = INVALID_LISTENER;
|
EventListener listener = INVALID_LISTENER;
|
||||||
|
|
||||||
if (IS_INVALID_SYSTEM(event_system) || IS_INVALID_EVENT(event) || event_system.id != event.system.id || !callback) {
|
if (!event_system || IS_INVALID_SYSTEM((*event_system)) || IS_INVALID_EVENT(event) || !callback) {
|
||||||
goto RETURN_LISTENER;
|
goto RETURN_LISTENER;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESInternal *es = get_event_system_internal(event_system);
|
ESInternal *es = get_event_system_internal(*event_system);
|
||||||
if (!VALID_MAGIC(es)) {
|
if (!VALID_MAGIC(es) || event.system != es->id) {
|
||||||
goto RETURN_LISTENER;
|
goto RETURN_LISTENER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,8 +239,8 @@ EventListener es_add_event_listener(EventSystem event_system, Event event, Event
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ev->count >= ev->capacity) {
|
if (ev->count >= ev->capacity) {
|
||||||
// TODO (Abdelrahman): Handle reallocating when out of callbacks
|
reallocate_system(event_system, es);
|
||||||
goto RETURN_LISTENER;
|
ev = get_event(es, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 id = ++(ev->count);
|
u64 id = ++(ev->count);
|
||||||
@@ -236,7 +255,7 @@ RETURN_LISTENER:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void es_remove_event_listener(EventSystem event_system, EventListener listener) {
|
void es_remove_event_listener(EventSystem event_system, EventListener listener) {
|
||||||
if (IS_INVALID_SYSTEM(event_system) || IS_INVALID_LISTENER(listener) || event_system.id != listener.event.system.id) {
|
if (IS_INVALID_SYSTEM(event_system) || IS_INVALID_LISTENER(listener)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,12 +283,12 @@ void es_remove_event_listener(EventSystem event_system, EventListener listener)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void es_emit_event(EventSystem event_system, Event event, void *data) {
|
void es_emit_event(EventSystem event_system, Event event, void *data) {
|
||||||
if (IS_INVALID_SYSTEM(event_system) || IS_INVALID_EVENT(event) || event_system.id != event.system.id) {
|
if (IS_INVALID_SYSTEM(event_system) || IS_INVALID_EVENT(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESInternal *es = get_event_system_internal(event_system);
|
ESInternal *es = get_event_system_internal(event_system);
|
||||||
if (!VALID_MAGIC(es) || event.id > es->event_count) {
|
if (!VALID_MAGIC(es) || event.system != es->id || event.id > es->event_count) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +321,41 @@ internal u64 *get_free_array(const ESInternal *es) {
|
|||||||
return (u64 *)((uptr)es + es->free_offset);
|
return (u64 *)((uptr)es + es->free_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void reallocate_system(EventSystem *event_system, ESInternal *es) {
|
||||||
|
u64 new_callback_capacity = get_largest_capacity(es);
|
||||||
|
EventSystem event_system_new = es_init(es->event_capacity, new_callback_capacity);
|
||||||
|
ESInternal *es_new = get_event_system_internal(event_system_new);
|
||||||
|
|
||||||
|
es_new->id = es->id;
|
||||||
|
es_new->event_count = es->event_count;
|
||||||
|
|
||||||
|
copy_event_data(es, es_new);
|
||||||
|
|
||||||
|
es_deinit(event_system);
|
||||||
|
event_system->id = event_system_new.id;
|
||||||
|
es = es_new;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void copy_event_data(const ESInternal *src, ESInternal *dst) {
|
||||||
|
EventSlot *slots_src = get_event_slots_array(src);
|
||||||
|
EventSlot *slots_dst = get_event_slots_array(dst);
|
||||||
|
|
||||||
|
EventInternal *ev_src;
|
||||||
|
EventInternal *ev_dst;
|
||||||
|
for (u64 i = 1; i <= src->event_count; ++i) {
|
||||||
|
slots_dst[i].registered = slots_src[i].registered;
|
||||||
|
|
||||||
|
ev_src = get_event_from_offset(src, slots_src[i].offset);
|
||||||
|
ev_dst = get_event_from_offset(dst, slots_dst[i].offset);
|
||||||
|
|
||||||
|
ev_dst->count = ev_src->count;
|
||||||
|
ev_dst->free_count = ev_src->free_count;
|
||||||
|
|
||||||
|
memcpy((void *)(ev_dst->free), (const void *)(ev_src->free), FREE_CB_ARR_SIZE(ev_src->capacity));
|
||||||
|
memcpy((void *)(ev_dst->callbacks), (const void *)(ev_src->callbacks), CB_ARR_SIZE(ev_src->capacity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal EventInternal *get_event(const ESInternal *es, Event event) {
|
internal EventInternal *get_event(const ESInternal *es, Event event) {
|
||||||
EventSlot *event_slots_array = get_event_slots_array(es);
|
EventSlot *event_slots_array = get_event_slots_array(es);
|
||||||
return get_event_from_offset(es, event_slots_array[event.id].offset);
|
return get_event_from_offset(es, event_slots_array[event.id].offset);
|
||||||
@@ -310,3 +364,18 @@ internal EventInternal *get_event(const ESInternal *es, Event event) {
|
|||||||
internal EventInternal *get_event_from_offset(const ESInternal *es, u64 offset) {
|
internal EventInternal *get_event_from_offset(const ESInternal *es, u64 offset) {
|
||||||
return (EventInternal *)((uptr)es + offset);
|
return (EventInternal *)((uptr)es + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal u64 get_largest_capacity(const ESInternal *es) {
|
||||||
|
EventSlot *event_slots_array = get_event_slots_array(es);
|
||||||
|
u64 capacity = es->initial_callback_capacity;
|
||||||
|
|
||||||
|
EventInternal *event;
|
||||||
|
for (u64 i = 1; i <= es->event_count; ++i) {
|
||||||
|
event = get_event_from_offset(es, event_slots_array[i].offset);
|
||||||
|
if (event->capacity > capacity) {
|
||||||
|
capacity = event->capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,6 @@
|
|||||||
|
|
||||||
#include "aliases.h"
|
#include "aliases.h"
|
||||||
|
|
||||||
enum {
|
|
||||||
ES_INIT_SUCCESS = 0,
|
|
||||||
ES_INIT_NULL_POINTER,
|
|
||||||
ES_INIT_ALREADY_INITIALISED,
|
|
||||||
ES_INIT_ALLOCATION_FAILED,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct event_system EventSystem;
|
typedef struct event_system EventSystem;
|
||||||
struct event_system {
|
struct event_system {
|
||||||
u64 id;
|
u64 id;
|
||||||
@@ -16,8 +9,8 @@ struct event_system {
|
|||||||
|
|
||||||
typedef struct event Event;
|
typedef struct event Event;
|
||||||
struct event {
|
struct event {
|
||||||
EventSystem system;
|
|
||||||
u64 id;
|
u64 id;
|
||||||
|
u64 system;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct event_listener EventListener;
|
typedef struct event_listener EventListener;
|
||||||
@@ -31,7 +24,7 @@ typedef void (*EventCallback)(void *data);
|
|||||||
EventSystem es_init(u64 initial_event_capacity, u64 initial_listeners_capacity);
|
EventSystem es_init(u64 initial_event_capacity, u64 initial_listeners_capacity);
|
||||||
void es_deinit(EventSystem *event_system);
|
void es_deinit(EventSystem *event_system);
|
||||||
Event es_register_event(EventSystem *event_system);
|
Event es_register_event(EventSystem *event_system);
|
||||||
void es_deregister_event(EventSystem *event_system, Event *event);
|
void es_deregister_event(EventSystem event_system, Event *event);
|
||||||
EventListener es_add_event_listener(EventSystem event_system, Event event, EventCallback callback);
|
EventListener es_add_event_listener(EventSystem *event_system, Event event, EventCallback callback);
|
||||||
void es_remove_event_listener(EventSystem event_system, EventListener listener);
|
void es_remove_event_listener(EventSystem event_system, EventListener listener);
|
||||||
void es_emit_event(EventSystem event_system, Event event, void *data);
|
void es_emit_event(EventSystem event_system, Event event, void *data);
|
||||||
|
|||||||
26
src/main.c
26
src/main.c
@@ -37,11 +37,11 @@ int main(void) {
|
|||||||
EventListener listeners[COUNT_EVENTS] = {0};
|
EventListener listeners[COUNT_EVENTS] = {0};
|
||||||
EventCallback callbacks[COUNT_EVENTS] = {window_event_handler, keyboard_event_handler, mouse_event_handler};
|
EventCallback callbacks[COUNT_EVENTS] = {window_event_handler, keyboard_event_handler, mouse_event_handler};
|
||||||
|
|
||||||
EventSystem es = es_init(COUNT_EVENTS, 64);
|
EventSystem es = es_init(COUNT_EVENTS, 4);
|
||||||
|
|
||||||
for (int i = 0; i < COUNT_EVENTS; ++i) {
|
for (int i = 0; i < COUNT_EVENTS; ++i) {
|
||||||
events[i] = es_register_event(&es);
|
events[i] = es_register_event(&es);
|
||||||
listeners[i] = es_add_event_listener(es, events[i], callbacks[i]);
|
listeners[i] = es_add_event_listener(&es, events[i], callbacks[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowEvent window_event = { .window_width = 1280, .window_height = 720 };
|
WindowEvent window_event = { .window_width = 1280, .window_height = 720 };
|
||||||
@@ -62,14 +62,30 @@ int main(void) {
|
|||||||
|
|
||||||
// You can also add other events not stored in an array
|
// You can also add other events not stored in an array
|
||||||
Event extra_event = es_register_event(&es);
|
Event extra_event = es_register_event(&es);
|
||||||
EventListener listener = es_add_event_listener(es, extra_event, extra_event_handler);
|
EventListener listener = es_add_event_listener(&es, extra_event, extra_event_handler);
|
||||||
es_emit_event(es, extra_event, NULL);
|
es_emit_event(es, extra_event, NULL);
|
||||||
es_remove_event_listener(es, listener);
|
es_remove_event_listener(es, listener);
|
||||||
es_deregister_event(&es, &extra_event);
|
es_deregister_event(es, &extra_event);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
es_register_event(&es);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("AFTER REALLOCATION WHEN OUT OF EVENTS\n\n");
|
||||||
|
es_emit_event(es, events[EVENT_WINDOW], (void *)&window_event);
|
||||||
|
es_emit_event(es, events[EVENT_KEYBOARD], (void *)&keyboard_event);
|
||||||
|
es_emit_event(es, events[EVENT_MOUSE], (void *)&mouse_event);
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
es_add_event_listener(&es, events[EVENT_WINDOW], callbacks[EVENT_WINDOW]);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("AFTER REALLOCATION WHEN OUT OF CALLBACKS\n\n");
|
||||||
|
es_emit_event(es, events[EVENT_WINDOW], (void *)&window_event);
|
||||||
|
|
||||||
for (int i = 0; i < COUNT_EVENTS; ++i) {
|
for (int i = 0; i < COUNT_EVENTS; ++i) {
|
||||||
es_remove_event_listener(es, listeners[i]);
|
es_remove_event_listener(es, listeners[i]);
|
||||||
es_deregister_event(&es, &events[i]);
|
es_deregister_event(es, &events[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
es_deinit(&es);
|
es_deinit(&es);
|
||||||
|
|||||||
Reference in New Issue
Block a user