Pico Headers
Loading...
Searching...
No Matches
pico_emitter.h
Go to the documentation of this file.
1
91#ifndef PICO_EMITTER_H
92#define PICO_EMITTER_H
93
94#include <stdbool.h> // bool, true, false
95#include <stddef.h> // NULL
96
97#ifdef __cplusplus
98extern "C" {
99#endif
100
107typedef void (*emitter_listener_fn)(const void* data, void* udata);
108
112typedef struct emitter_s emitter_t;
113
123emitter_t* emitter_create(int num_events);
124
131
147bool emitter_resize(emitter_t* emitter, int num_events);
148
160void emitter_on(emitter_t* emitter, int event, emitter_listener_fn listener, void* udata);
161
173void emitter_once(emitter_t* emitter, int event, emitter_listener_fn listener, void* udata);
174
185void emitter_off(emitter_t* emitter, int event, emitter_listener_fn listener);
186
193void emitter_off_all(emitter_t* emitter, int event);
194
206void emitter_emit(emitter_t* emitter, int event, const void* data);
207
216int emitter_count(const emitter_t* emitter, int event);
217
218/* --------------------------------------------------------------------------
219 * Queued emitter
220 * -------------------------------------------------------------------------- */
221
230typedef struct queued_emitter_s queued_emitter_t;
231
240
247
263bool queued_emitter_resize(queued_emitter_t* qe, int num_events);
264
273void queued_emitter_on(queued_emitter_t* qe, int event, emitter_listener_fn listener, void* udata);
274
283void queued_emitter_once(queued_emitter_t* qe, int event, emitter_listener_fn listener, void* udata);
284
293
301
314void queued_emitter_emit(queued_emitter_t* qe, int event, const void* data);
315
330void queued_emitter_enqueue_raw(queued_emitter_t* qe, int event, const void* data, size_t data_size);
331
352#define queued_emitter_enqueue(qe, event, ptr) \
353 (queued_emitter_enqueue_raw((qe), (event), (ptr), sizeof(*(ptr))))
354
363
372int queued_emitter_count(const queued_emitter_t* qe, int event);
373
374#ifdef __cplusplus
375}
376#endif
377
378#endif // PICO_EMITTER_H
379
380#ifdef PICO_EMITTER_IMPLEMENTATION
381
382#include <stdalign.h> // alignof
383#include <stdint.h> // uint8_t
384#include <string.h> // memset, memcpy
385
386/* --------------------------------------------------------------------------
387 * Configuration
388 * -------------------------------------------------------------------------- */
389
390#ifndef PICO_EMITTER_INIT_CAPACITY
391 #define PICO_EMITTER_INIT_CAPACITY 8
392#endif
393
394#ifdef NDEBUG
395 #define PICO_EMITTER_ASSERT(expr) ((void)0)
396#else
397 #ifndef PICO_EMITTER_ASSERT
398 #include <assert.h>
399 #define PICO_EMITTER_ASSERT(expr) assert(expr)
400 #endif
401#endif
402
403#ifndef PICO_EMITTER_MALLOC
404 #include <stdlib.h>
405 #define PICO_EMITTER_MALLOC(size) malloc(size)
406 #define PICO_EMITTER_REALLOC(p, sz) realloc(p, sz)
407 #define PICO_EMITTER_FREE(ptr) free(ptr)
408#endif
409
410#ifndef PICO_EMITTER_MEMCPY
411 #include <string.h>
412 #define PICO_EMITTER_MEMCPY(dst, src, n) memcpy(dst, src, n)
413#endif
414
415#ifndef PICO_EMITTER_MEMSET
416 #include <string.h>
417 #define PICO_EMITTER_MEMSET(dst, val, n) memset(dst, val, n)
418#endif
419
420/* --------------------------------------------------------------------------
421 * Internal aliases
422 * -------------------------------------------------------------------------- */
423
424#define EMITTER_INIT_CAPACITY PICO_EMITTER_INIT_CAPACITY
425#define EMITTER_ASSERT PICO_EMITTER_ASSERT
426#define EMITTER_MALLOC PICO_EMITTER_MALLOC
427#define EMITTER_REALLOC PICO_EMITTER_REALLOC
428#define EMITTER_FREE PICO_EMITTER_FREE
429#define EMITTER_MEMCPY PICO_EMITTER_MEMCPY
430#define EMITTER_MEMSET PICO_EMITTER_MEMSET
431
432/* --------------------------------------------------------------------------
433 * Emitter types
434 * -------------------------------------------------------------------------- */
435
436/*
437 * Per-event listener slot. Uses a struct-of-arrays layout so that function
438 * pointers are contiguous in memory, improving cache performance during emit.
439 * Arrays are heap-allocated and grow by doubling when capacity is exceeded.
440 */
441typedef struct
442{
443 emitter_listener_fn* listeners; // function pointers
444 void** udatas; // corresponding user data
445 bool* once; // fire-once flag
446 int count; // active listener count
447 int capacity; // allocated capacity
448 bool emitting; // true while iterating
449} emitter_slot_t;
450
451struct emitter_s
452{
453 emitter_slot_t* events;
454 int num_events;
455};
456
457/* --------------------------------------------------------------------------
458 * Arena allocator types
459 * -------------------------------------------------------------------------- */
460
461typedef struct arena_block_s
462{
463 uint8_t* memory;
464 size_t size;
465 size_t offset;
466 struct arena_block_s* next;
467} arena_block_t;
468
469typedef struct arena_s
470{
471 arena_block_t* first;
472 arena_block_t* current;
473 size_t block_size;
474} arena_t;
475
476/* --------------------------------------------------------------------------
477 * Forward declarations
478 * -------------------------------------------------------------------------- */
479
480static void emitter_grow_slot(emitter_slot_t* slot);
481static void emitter_compact(emitter_slot_t* slot);
482static void emitter_subscribe(emitter_t* emitter, int event, emitter_listener_fn listener, void* udata, bool once);
483static void queued_emitter_grow(queued_emitter_t* qe);
484static arena_block_t* arena_block_create(size_t size);
485static bool arena_init(arena_t* arena, size_t initial_block_size);
486static bool arena_grow(arena_t* arena, size_t min_size);
487static uintptr_t arena_align_forward(uintptr_t ptr, size_t align);
488static void* arena_alloc_align(arena_t* arena, size_t size, size_t align);
489static void* arena_alloc(arena_t* arena, size_t size);
490static void arena_reset(arena_t* arena);
491static void arena_destroy(arena_t* arena);
492
493/* --------------------------------------------------------------------------
494 * Emitter public API implementation
495 * -------------------------------------------------------------------------- */
496
497emitter_t* emitter_create(int num_events)
498{
499 EMITTER_ASSERT(num_events > 0);
500
501 emitter_t* emitter = (emitter_t*)EMITTER_MALLOC(sizeof(emitter_t));
502
503 if (!emitter)
504 {
505 return NULL;
506 }
507
508 emitter->events = (emitter_slot_t*)EMITTER_MALLOC((size_t)num_events * sizeof(emitter_slot_t));
509
510 if (!emitter->events)
511 {
512 EMITTER_FREE(emitter);
513 return NULL;
514 }
515
516 EMITTER_MEMSET(emitter->events, 0, (size_t)num_events * sizeof(emitter_slot_t));
517
518 emitter->num_events = num_events;
519
520 return emitter;
521}
522
523void emitter_destroy(emitter_t* emitter)
524{
525 EMITTER_ASSERT(emitter != NULL);
526
527 for (int i = 0; i < emitter->num_events; i++)
528 {
529 EMITTER_FREE(emitter->events[i].listeners);
530 EMITTER_FREE(emitter->events[i].udatas);
531 EMITTER_FREE(emitter->events[i].once);
532 }
533
534 EMITTER_FREE(emitter->events);
535 EMITTER_FREE(emitter);
536}
537
538bool emitter_resize(emitter_t* emitter, int num_events)
539{
540 EMITTER_ASSERT(emitter != NULL);
541 EMITTER_ASSERT(num_events > 0);
542
543 int old_num = emitter->num_events;
544
545 if (num_events == old_num)
546 {
547 return true;
548 }
549
550 // When shrinking, release the listener arrays of the slots being dropped
551 // before they become unreachable past the reallocated boundary.
552 for (int i = num_events; i < old_num; i++)
553 {
554 EMITTER_FREE(emitter->events[i].listeners);
555 EMITTER_FREE(emitter->events[i].udatas);
556 EMITTER_FREE(emitter->events[i].once);
557 // Clear the slot so a failed shrink-realloc below cannot leave dangling
558 // freed pointers that emitter_destroy would later double-free.
559 EMITTER_MEMSET(&emitter->events[i], 0, sizeof(emitter_slot_t));
560 }
561
562 emitter_slot_t* events = (emitter_slot_t*)EMITTER_REALLOC(emitter->events, (size_t)num_events * sizeof(emitter_slot_t));
563
564 if (!events)
565 {
566 return false;
567 }
568
569 // When growing, the newly added slots must start empty.
570 if (num_events > old_num)
571 {
572 EMITTER_MEMSET(&events[old_num], 0, (size_t)(num_events - old_num) * sizeof(emitter_slot_t));
573 }
574
575 emitter->events = events;
576 emitter->num_events = num_events;
577
578 return true;
579}
580
581void emitter_on(emitter_t* emitter, int event, emitter_listener_fn listener, void* udata)
582{
583 emitter_subscribe(emitter, event, listener, udata, false);
584}
585
586void emitter_once(emitter_t* emitter, int event, emitter_listener_fn listener, void* udata)
587{
588 emitter_subscribe(emitter, event, listener, udata, true);
589}
590
591void emitter_off(emitter_t* emitter, int event, emitter_listener_fn listener)
592{
593 EMITTER_ASSERT(emitter != NULL);
594 EMITTER_ASSERT(event >= 0 && event < emitter->num_events);
595 EMITTER_ASSERT(listener != NULL);
596
597 emitter_slot_t* slot = &emitter->events[event];
598
599 for (int i = 0; i < slot->count; i++)
600 {
601 if (slot->listeners[i] != listener)
602 {
603 continue;
604 }
605
606 if (slot->emitting)
607 {
608 // Tombstone; emitter_emit will compact after iteration.
609 slot->listeners[i] = NULL;
610 }
611 else
612 {
613 // Compact in-place immediately.
614 for (int j = i; j < slot->count - 1; j++)
615 {
616 slot->listeners[j] = slot->listeners[j + 1];
617 slot->udatas[j] = slot->udatas[j + 1];
618 slot->once[j] = slot->once[j + 1];
619 }
620
621 slot->count--;
622 }
623
624 return; // Remove first match only.
625 }
626}
627
628void emitter_off_all(emitter_t* emitter, int event)
629{
630 EMITTER_ASSERT(emitter != NULL);
631 EMITTER_ASSERT(event >= 0 && event < emitter->num_events);
632
633 emitter_slot_t* slot = &emitter->events[event];
634
635 if (slot->emitting)
636 {
637 // Tombstone all; emitter_emit will compact after iteration.
638 for (int i = 0; i < slot->count; i++)
639 {
640 slot->listeners[i] = NULL;
641 }
642 }
643 else
644 {
645 slot->count = 0;
646 }
647}
648
649void emitter_emit(emitter_t* emitter, int event, const void* data)
650{
651 EMITTER_ASSERT(emitter != NULL);
652 EMITTER_ASSERT(event >= 0 && event < emitter->num_events);
653
654 emitter_slot_t* slot = &emitter->events[event];
655
656 /*
657 * Capture the count before iterating so that listeners added during emit
658 * are not called in the current dispatch.
659 */
660 int count = slot->count;
661 bool needs_compact = false;
662
663 slot->emitting = true;
664
665 for (int i = 0; i < count; i++)
666 {
667 emitter_listener_fn fn = slot->listeners[i];
668
669 if (!fn)
670 {
671 // Tombstoned by emitter_off or emitter_off_all from a prior callback.
672 needs_compact = true;
673 continue;
674 }
675
676 /*
677 * Tombstone once-listeners before calling so that re-registration
678 * from within the callback is safe.
679 */
680 if (slot->once[i])
681 {
682 slot->listeners[i] = NULL;
683 needs_compact = true;
684 }
685
686 fn(data, slot->udatas[i]);
687
688 /*
689 * The listener may have tombstoned itself (or a later slot) via
690 * emitter_off during the callback. Check whether this slot became NULL
691 * after the call.
692 */
693 if (slot->listeners[i] == NULL)
694 {
695 needs_compact = true;
696 }
697 }
698
699 slot->emitting = false;
700
701 if (needs_compact)
702 {
703 emitter_compact(slot);
704 }
705}
706
707int emitter_count(const emitter_t* emitter, int event)
708{
709 EMITTER_ASSERT(emitter != NULL);
710 EMITTER_ASSERT(event >= 0 && event < emitter->num_events);
711
712 return emitter->events[event].count;
713}
714
715/* --------------------------------------------------------------------------
716 * Queued Emitter types
717 * -------------------------------------------------------------------------- */
718
719struct queued_emitter_s
720{
721 emitter_t* emitter;
722 arena_t arenas[2]; // ping-pong pair
723 int write_arena; // index of the arena new emits write into
724 int* events;
725 const void** datas;
726 int count;
727 int capacity;
728};
729
730/* --------------------------------------------------------------------------
731 * Queued Emitter public API implementation
732 * -------------------------------------------------------------------------- */
733
735{
736 EMITTER_ASSERT(num_events > 0);
737
738 queued_emitter_t* qe = (queued_emitter_t*)EMITTER_MALLOC(sizeof(queued_emitter_t));
739
740 if (!qe)
741 {
742 return NULL;
743 }
744
745 qe->emitter = emitter_create(num_events);
746
747 if (!qe->emitter)
748 {
749 EMITTER_FREE(qe);
750 return NULL;
751 }
752
753 size_t init_block = (size_t)EMITTER_INIT_CAPACITY * sizeof(void*);
754
755 if (!arena_init(&qe->arenas[0], init_block))
756 {
757 emitter_destroy(qe->emitter);
758 EMITTER_FREE(qe);
759 return NULL;
760 }
761
762 if (!arena_init(&qe->arenas[1], init_block))
763 {
764 arena_destroy(&qe->arenas[0]);
765 emitter_destroy(qe->emitter);
766 EMITTER_FREE(qe);
767 return NULL;
768 }
769
770 qe->write_arena = 0;
771 qe->events = NULL;
772 qe->datas = NULL;
773 qe->count = 0;
774 qe->capacity = 0;
775
776 return qe;
777}
778
780{
781 EMITTER_ASSERT(qe != NULL);
782
783 emitter_destroy(qe->emitter);
784 arena_destroy(&qe->arenas[0]);
785 arena_destroy(&qe->arenas[1]);
786 EMITTER_FREE(qe->events);
787 EMITTER_FREE(qe->datas);
788 EMITTER_FREE(qe);
789}
790
791bool queued_emitter_resize(queued_emitter_t* qe, int num_events)
792{
793 EMITTER_ASSERT(qe != NULL);
794 return emitter_resize(qe->emitter, num_events);
795}
796
797void queued_emitter_on(queued_emitter_t* qe, int event, emitter_listener_fn listener, void* udata)
798{
799 EMITTER_ASSERT(qe != NULL);
800 emitter_on(qe->emitter, event, listener, udata);
801}
802
803void queued_emitter_once(queued_emitter_t* qe, int event, emitter_listener_fn listener, void* udata)
804{
805 EMITTER_ASSERT(qe != NULL);
806 emitter_once(qe->emitter, event, listener, udata);
807}
808
809void queued_emitter_off(queued_emitter_t* qe, int event, emitter_listener_fn listener)
810{
811 EMITTER_ASSERT(qe != NULL);
812 emitter_off(qe->emitter, event, listener);
813}
814
815void queued_emitter_off_all(queued_emitter_t* qe, int event)
816{
817 EMITTER_ASSERT(qe != NULL);
818 emitter_off_all(qe->emitter, event);
819}
820
821void queued_emitter_emit(queued_emitter_t* qe, int event, const void* data)
822{
823 EMITTER_ASSERT(qe != NULL);
824 emitter_emit(qe->emitter, event, data);
825}
826
827void queued_emitter_enqueue_raw(queued_emitter_t* qe, int event, const void* data, size_t data_size)
828{
829 EMITTER_ASSERT(qe != NULL);
830 EMITTER_ASSERT(event >= 0 && event < qe->emitter->num_events);
831
832 if (qe->count == qe->capacity)
833 {
834 queued_emitter_grow(qe);
835 }
836
837 const void* stored = NULL;
838
839 if (data != NULL && data_size > 0)
840 {
841 void* copy = arena_alloc(&qe->arenas[qe->write_arena], data_size);
842 EMITTER_ASSERT(copy != NULL);
843 EMITTER_MEMCPY(copy, data, data_size);
844 stored = copy;
845 }
846
847 qe->events[qe->count] = event;
848 qe->datas[qe->count] = stored;
849 qe->count++;
850}
851
853{
854 EMITTER_ASSERT(qe != NULL);
855
856 int remaining = 0;
857
858 do {
859
860 int flush_count = qe->count;
861 int read_arena = qe->write_arena;
862
863 /* Flip write arena before dispatching so that nested emits during flush
864 * copy payload into the other arena. Their data pointers remain valid
865 * after the read arena is reset below. */
866 qe->write_arena = 1 - read_arena;
867
868 for (int i = 0; i < flush_count; i++)
869 {
870 emitter_emit(qe->emitter, qe->events[i], qe->datas[i]);
871 }
872
873 /* Shift deferred events (enqueued during flush) to the front.
874 * events/datas are heap-resident so no arena pointer fixup is needed. */
875 remaining = qe->count - flush_count;
876
877 for (int i = 0; i < remaining; i++)
878 {
879 qe->events[i] = qe->events[flush_count + i];
880 qe->datas[i] = qe->datas[flush_count + i];
881 }
882
883 qe->count = remaining;
884
885 // All dispatched payloads are consumed — O(1) reset of the read arena.
886 arena_reset(&qe->arenas[read_arena]);
887
888 } while (remaining > 0);
889}
890
891int queued_emitter_count(const queued_emitter_t* qe, int event)
892{
893 EMITTER_ASSERT(qe != NULL);
894 return emitter_count(qe->emitter, event);
895}
896
897/* --------------------------------------------------------------------------
898 * Emitter static helpers
899 * -------------------------------------------------------------------------- */
900
901/*
902 * Grows a slot's listener arrays by doubling capacity.
903 */
904static void emitter_grow_slot(emitter_slot_t* slot)
905{
906 int new_cap = slot->capacity == 0 ? EMITTER_INIT_CAPACITY : slot->capacity * 2;
907
908 emitter_listener_fn* l = (emitter_listener_fn*)EMITTER_REALLOC(slot->listeners, (size_t)new_cap * sizeof(emitter_listener_fn));
909 void** u = (void**) EMITTER_REALLOC(slot->udatas, (size_t)new_cap * sizeof(void*));
910 bool* o = (bool*) EMITTER_REALLOC(slot->once, (size_t)new_cap * sizeof(bool));
911
912 EMITTER_ASSERT(l != NULL);
913 EMITTER_ASSERT(u != NULL);
914 EMITTER_ASSERT(o != NULL);
915
916 slot->listeners = l;
917 slot->udatas = u;
918 slot->once = o;
919 slot->capacity = new_cap;
920}
921
922/*
923 * Removes NULL-tombstoned entries from a slot after emit or emitter_off.
924 */
925static void emitter_compact(emitter_slot_t* slot)
926{
927 int dst = 0;
928
929 for (int src = 0; src < slot->count; src++)
930 {
931 if (slot->listeners[src] != NULL)
932 {
933 slot->listeners[dst] = slot->listeners[src];
934 slot->udatas[dst] = slot->udatas[src];
935 slot->once[dst] = slot->once[src];
936 dst++;
937 }
938 }
939
940 slot->count = dst;
941}
942
943/*
944 * Internal helper for both emitter_on and emitter_once.
945 */
946static void emitter_subscribe(emitter_t* emitter, int event,
947 emitter_listener_fn listener, void* udata,
948 bool once)
949{
950 EMITTER_ASSERT(emitter != NULL);
951 EMITTER_ASSERT(event >= 0 && event < emitter->num_events);
952 EMITTER_ASSERT(listener != NULL);
953
954 emitter_slot_t* slot = &emitter->events[event];
955
956 if (slot->count == slot->capacity)
957 {
958 emitter_grow_slot(slot);
959 }
960
961 slot->listeners[slot->count] = listener;
962 slot->udatas[slot->count] = udata;
963 slot->once[slot->count] = once;
964 slot->count++;
965}
966
967/* --------------------------------------------------------------------------
968 * Queued Emitter static helpers
969 * -------------------------------------------------------------------------- */
970
971static void queued_emitter_grow(queued_emitter_t* qe)
972{
973 int new_cap = qe->capacity == 0 ? EMITTER_INIT_CAPACITY : qe->capacity * 2;
974
975 int* events = (int*) EMITTER_REALLOC(qe->events, (size_t)new_cap * sizeof(int));
976 const void** datas = (const void**)EMITTER_REALLOC(qe->datas, (size_t)new_cap * sizeof(const void*));
977
978 EMITTER_ASSERT(events != NULL);
979 EMITTER_ASSERT(datas != NULL);
980
981 qe->events = events;
982 qe->datas = datas;
983 qe->capacity = new_cap;
984}
985
986/* --------------------------------------------------------------------------
987 * Arena allocator implementation
988 * -------------------------------------------------------------------------- */
989
990static arena_block_t* arena_block_create(size_t size)
991{
992 arena_block_t* block = (arena_block_t*)EMITTER_MALLOC(sizeof(arena_block_t));
993
994 if (!block)
995 return NULL;
996
997 block->memory = (uint8_t*)EMITTER_MALLOC(size);
998
999 if (!block->memory)
1000 {
1001 EMITTER_FREE(block);
1002 return NULL;
1003 }
1004
1005 block->size = size;
1006 block->offset = 0;
1007 block->next = NULL;
1008
1009 return block;
1010}
1011
1012static bool arena_init(arena_t* arena, size_t initial_block_size)
1013{
1014 arena_block_t* block = arena_block_create(initial_block_size);
1015
1016 if (!block)
1017 return false;
1018
1019 arena->first = block;
1020 arena->current = block;
1021 arena->block_size = initial_block_size;
1022
1023 return true;
1024}
1025
1026static bool arena_grow(arena_t* arena, size_t min_size)
1027{
1028 size_t new_size = arena->block_size;
1029
1030 while (new_size < min_size)
1031 {
1032 new_size *= 2;
1033 }
1034
1035 arena_block_t* block = arena_block_create(new_size);
1036
1037 if (!block)
1038 return false;
1039
1040 arena->current->next = block;
1041 arena->current = block;
1042
1043 return true;
1044}
1045
1046static uintptr_t arena_align_forward(uintptr_t ptr, size_t align)
1047{
1048 uintptr_t mask = (uintptr_t)align - 1;
1049 return (ptr + mask) & ~mask;
1050}
1051
1052static void* arena_alloc_align(arena_t* arena, size_t size, size_t align)
1053{
1054 if ((align & (align - 1)) != 0)
1055 return NULL; // align must be a power of two
1056
1057 arena_block_t* block = arena->current;
1058
1059 uintptr_t base = (uintptr_t)block->memory;
1060 uintptr_t ptr = base + block->offset;
1061 uintptr_t aligned = arena_align_forward(ptr, align);
1062 size_t new_offset = (aligned - base) + size;
1063
1064 if (new_offset > block->size)
1065 {
1066 size_t required = size + align;
1067
1068 if (!arena_grow(arena, required))
1069 return NULL;
1070
1071 block = arena->current;
1072 base = (uintptr_t)block->memory;
1073 aligned = arena_align_forward(base, align);
1074 new_offset = (aligned - base) + size;
1075 }
1076
1077 block->offset = new_offset;
1078
1079 return (void*)aligned;
1080}
1081
1082static void* arena_alloc(arena_t* arena, size_t size)
1083{
1084 return arena_alloc_align(arena, size, alignof(max_align_t));
1085}
1086
1087static void arena_reset(arena_t* arena)
1088{
1089 arena_block_t* block = arena->first->next;
1090
1091 while (block)
1092 {
1093 arena_block_t* next = block->next;
1094 EMITTER_FREE(block->memory);
1095 EMITTER_FREE(block);
1096 block = next;
1097 }
1098
1099 arena->first->next = NULL;
1100 arena->first->offset = 0;
1101 arena->current = arena->first;
1102}
1103
1104static void arena_destroy(arena_t* arena)
1105{
1106 arena_block_t* block = arena->first;
1107
1108 while (block)
1109 {
1110 arena_block_t* next = block->next;
1111 EMITTER_FREE(block->memory);
1112 EMITTER_FREE(block);
1113 block = next;
1114 }
1115
1116 arena->first = NULL;
1117 arena->current = NULL;
1118}
1119
1120#endif // PICO_EMITTER_IMPLEMENTATION
1121
1122/*
1123 ----------------------------------------------------------------------------
1124 This software is available under two licenses (A) or (B). You may choose
1125 either one as you wish:
1126 ----------------------------------------------------------------------------
1127
1128 (A) The MIT License
1129
1130 Copyright (c) 2026 James McLean
1131
1132 Permission is hereby granted, free of charge, to any person obtaining a copy
1133 of this software and associated documentation files (the "Software"), to
1134 deal in the Software without restriction, including without limitation the
1135 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
1136 sell copies of the Software, and to permit persons to whom the Software is
1137 furnished to do so, subject to the following conditions:
1138
1139 The above copyright notice and this permission notice shall be included in
1140 all copies or substantial portions of the Software.
1141
1142 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1143 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1144 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1145 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1146 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1147 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
1148 IN THE SOFTWARE.
1149
1150 ----------------------------------------------------------------------------
1151
1152 (B) Public Domain (www.unlicense.org)
1153
1154 This is free and unencumbered software released into the public domain.
1155
1156 Anyone is free to copy, modify, publish, use, compile, sell, or distribute
1157 this software, either in source code form or as a compiled binary, for any
1158 purpose, commercial or non-commercial, and by any means.
1159
1160 In jurisdictions that recognize copyright laws, the author or authors of
1161 this software dedicate any and all copyright interest in the software to the
1162 public domain. We make this dedication for the benefit of the public at
1163 large and to the detriment of our heirs and successors. We intend this
1164 dedication to be an overt act of relinquishment in perpetuity of all present
1165 and future rights to this software under copyright law.
1166
1167 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1168 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1169 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1170 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1171 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1172 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1173*/
1174
1175// EoF
int queued_emitter_count(const queued_emitter_t *qe, int event)
Returns the number of listeners currently subscribed to an event.
void queued_emitter_off(queued_emitter_t *qe, int event, emitter_listener_fn listener)
Unsubscribes the first listener whose function pointer matches.
void queued_emitter_enqueue_raw(queued_emitter_t *qe, int event, const void *data, size_t data_size)
Enqueues an event for deferred dispatch.
struct emitter_s emitter_t
Event emitter context (opaque).
Definition pico_emitter.h:112
void queued_emitter_off_all(queued_emitter_t *qe, int event)
Removes all listeners subscribed to an event.
void emitter_off_all(emitter_t *emitter, int event)
Removes all listeners subscribed to an event.
struct queued_emitter_s queued_emitter_t
Queued event emitter context (opaque).
Definition pico_emitter.h:230
void emitter_off(emitter_t *emitter, int event, emitter_listener_fn listener)
Unsubscribes the first listener whose function pointer matches.
void queued_emitter_once(queued_emitter_t *qe, int event, emitter_listener_fn listener, void *udata)
Subscribes a listener that fires exactly once, then unsubscribes.
void emitter_once(emitter_t *emitter, int event, emitter_listener_fn listener, void *udata)
Subscribes a listener that fires exactly once, then unsubscribes.
int emitter_count(const emitter_t *emitter, int event)
Returns the number of listeners currently subscribed to an event.
emitter_t * emitter_create(int num_events)
Creates an event emitter that supports the given number of event types.
void queued_emitter_flush(queued_emitter_t *qe)
Dispatches all queued events in FIFO order and clears the queue.
bool queued_emitter_resize(queued_emitter_t *qe, int num_events)
Resizes the queued emitter to support a different number of event types.
void emitter_on(emitter_t *emitter, int event, emitter_listener_fn listener, void *udata)
Subscribes a listener to an event.
void emitter_destroy(emitter_t *emitter)
Destroys the emitter and frees all associated memory.
void(* emitter_listener_fn)(const void *data, void *udata)
Listener callback signature.
Definition pico_emitter.h:107
void queued_emitter_on(queued_emitter_t *qe, int event, emitter_listener_fn listener, void *udata)
Subscribes a persistent listener to an event.
queued_emitter_t * queued_emitter_create(int num_events)
Creates a queued emitter supporting the given number of event types.
void queued_emitter_destroy(queued_emitter_t *qe)
Destroys the queued emitter and discards any unflushed events.
void queued_emitter_emit(queued_emitter_t *qe, int event, const void *data)
Emits an event immediately, bypassing the queue.
bool emitter_resize(emitter_t *emitter, int num_events)
Resizes the emitter to support a different number of event types.
void emitter_emit(emitter_t *emitter, int event, const void *data)
Emits an event, invoking all registered listeners in order.