upubsub.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /* upubsub: a minimal publish/subscribe 1 header file library for pure ANSI C. */
  2. #ifndef UPUBSUB_H_INCLUDED
  3. #define UPUBSUB_H_INCLUDED
  4. #ifndef UPUBSUB_LISTENER_AMOUNT
  5. #define UPUBSUB_LISTENER_AMOUNT 64
  6. #endif /* UPUBSUB_LISTENER_AMOUNT */
  7. #ifdef UPUBSUB_TEST
  8. struct upubsub_test_message_type {
  9. char text[64];
  10. int x;
  11. int y;
  12. };
  13. #define UPUBSUB_MESSAGE_TYPE struct upubsub_test_message_type
  14. #endif
  15. #ifndef UPUBSUB_MESSAGE_SIZE
  16. #ifndef UPUBSUB_MESSAGE_TYPE
  17. #define UPUBSUB_MESSAGE_SIZE 64
  18. #else
  19. #define UPUBSUB_MESSAGE_SIZE (sizeof(UPUBSUB_MESSAGE_TYPE))
  20. #endif
  21. #endif /* UPUBSUB_MESSAGE_SIZE */
  22. #ifndef UPUBSUB_TOPIC_AMOUNT
  23. #define UPUBSUB_TOPIC_AMOUNT 32
  24. #endif /* UPUBSUB_TOPIC_AMOUNT */
  25. #ifdef __cplusplus
  26. extern "C" {
  27. #endif
  28. #include <stdlib.h>
  29. #include <stdint.h>
  30. #include <string.h>
  31. enum upubsub_message_data_type {
  32. upubsub_message_data_type_bytes = 1,
  33. upubsub_message_data_type_ptr = 2,
  34. upubsub_message_data_type_typed = 3,
  35. };
  36. struct upubsub_message {
  37. const char * topic;
  38. size_t size;
  39. enum upubsub_message_data_type data_type;
  40. union {
  41. uint8_t bytes[UPUBSUB_MESSAGE_SIZE];
  42. void * ptr;
  43. #ifdef UPUBSUB_MESSAGE_TYPE
  44. UPUBSUB_MESSAGE_TYPE typed;
  45. #endif
  46. } data;
  47. };
  48. struct upubsub_listener;
  49. struct upubsub;
  50. typedef int (upubsub_listen_func)(struct upubsub_listener *l, struct upubsub_message m);
  51. struct upubsub_listener {
  52. struct upubsub * upubsub;
  53. unsigned int id;
  54. const char * topic;
  55. void * data;
  56. upubsub_listen_func * listen;
  57. };
  58. struct upubsub {
  59. size_t size;
  60. unsigned int last_id;
  61. struct upubsub_listener listeners[UPUBSUB_LISTENER_AMOUNT];
  62. size_t topics_size;
  63. };
  64. int upubsub_listener_cmp_topic_id(const void * v1, const void * v2);
  65. int upubsub_listener_cmp_topic(const void * v1, const void * v2);
  66. int upubsub_listener_cmp_id(const void * v1, const void * v2);
  67. struct upubsub_listener * upubsub_subscribe_listener(struct upubsub * u, struct upubsub_listener l);
  68. struct upubsub_listener* upubsub_subscribe(struct upubsub * u, const char * topic, void * data, upubsub_listen_func * listen);
  69. int upubsub_publish_message(struct upubsub u, struct upubsub_message m);
  70. int upubsub_publish_data(struct upubsub u, const char * topic, void * data, size_t size);
  71. int upubsub_publish_str(struct upubsub u, const char * topic, char * str);
  72. int upubsub_unsubscribe_listener(struct upubsub * u, struct upubsub_listener * l);
  73. #ifdef UPUBSUB_MESSAGE_TYPE
  74. int upubsub_publish_type(struct upubsub u, const char * topic, UPUBSUB_MESSAGE_TYPE m);
  75. #endif
  76. #ifdef __cplusplus
  77. }
  78. #endif
  79. #endif /* UPUBSUB_H_INCLUDED */
  80. #ifdef UPUBSUB_TEST
  81. #define UPUBSUB_IMPLEMENTATION
  82. #endif
  83. #ifdef UPUBSUB_IMPLEMENTATION
  84. #ifdef __cplusplus
  85. #error This is not C++. Please compile this with a C compiler.
  86. #endif
  87. /** Result of searching with brange_search. If high < low, nothing was found. */
  88. struct bsearch_range_result {
  89. int low;
  90. int high;
  91. };
  92. struct bsearch_range_result bsearch_range (const void *key, const void *base, size_t nel, size_t width, int (*cmp)(const void *, const void *)) {
  93. struct bsearch_range_result res;
  94. void *here;
  95. int sign;
  96. int botm = 0;
  97. int top = nel;
  98. res.low = 0;
  99. res.high = nel;
  100. while (res.low <= top) {
  101. int mid = res.low + (top - res.low) / 2;
  102. here = (char*)base + (mid*width);
  103. sign = cmp(key, here);
  104. if (sign <= 0) {
  105. top = mid - 1;
  106. } else {
  107. res.low = mid + 1;
  108. }
  109. }
  110. botm = res.low ;
  111. while (botm <= res.high) {
  112. int mid = botm + (res.high - botm) / 2;
  113. here = (char*)base + (mid*width);
  114. sign = cmp(key, here);
  115. if (sign < 0) {
  116. res.high = mid - 1;
  117. } else {
  118. botm = mid + 1;
  119. }
  120. }
  121. return res;
  122. }
  123. int upubsub_listener_cmp_topic_id(const void * v1, const void * v2) {
  124. int res;
  125. struct upubsub_listener *l1, *l2;
  126. l1 = (struct upubsub_listener *)(v1);
  127. l2 = (struct upubsub_listener *)(v2);
  128. /* Sort with null topic as last, as these ones are unused. */
  129. if ((!l1->topic) && (l2->topic)) { return 1; }
  130. if ((!l2->topic) && (l1->topic)) { return -1; }
  131. if ((!l2->topic) && (!l1->topic)) { return 0; }
  132. /* Otherwise compare topics. */
  133. res = strcmp(l1->topic, l2->topic);
  134. /* If the same sort by id to try to keep the sort stable. */
  135. if (!res) {
  136. return l1->id - l2->id;
  137. }
  138. return res;
  139. }
  140. int upubsub_listener_cmp_topic(const void * v1, const void * v2) {
  141. struct upubsub_listener *l1, *l2;
  142. l1 = (struct upubsub_listener *)(v1);
  143. l2 = (struct upubsub_listener *)(v2);
  144. /* Sort with null topic as last, as these ones are unused. */
  145. if ((!l1->topic) && (l2->topic)) { return 1; }
  146. if ((!l2->topic) && (l1->topic)) { return -1; }
  147. if ((!l2->topic) && (!l1->topic)) { return 0; }
  148. /* Otherwise compare topics. */
  149. return strcmp(l1->topic, l2->topic);
  150. }
  151. int upubsub_listener_cmp_id(const void * v1, const void * v2) {
  152. struct upubsub_listener *l1, *l2;
  153. l1 = (struct upubsub_listener *)(v1);
  154. l2 = (struct upubsub_listener *)(v2);
  155. /* Sort with null topic as last, as these ones are unused. */
  156. if ((!l1->topic) && (l2->topic)) { return 1; }
  157. if ((!l2->topic) && (l1->topic)) { return -1; }
  158. if ((!l2->topic) && (!l1->topic)) { return 0; }
  159. /* Compare by id to find the exact listener. */
  160. return l1->id - l2->id;
  161. }
  162. struct upubsub_listener * upubsub_subscribe_listener(struct upubsub * u, struct upubsub_listener l) {
  163. struct upubsub_listener * res;
  164. if ( u->size >= UPUBSUB_LISTENER_AMOUNT ) {
  165. return NULL;
  166. }
  167. u->last_id++;
  168. l.id = u->last_id;
  169. l.upubsub = u;
  170. u->listeners[u->size] = l;
  171. res = u->listeners + u->size;
  172. u->size ++;
  173. qsort(u->listeners, u->size, sizeof(struct upubsub_listener), upubsub_listener_cmp_topic_id);
  174. return res;
  175. }
  176. struct upubsub_listener* upubsub_subscribe(struct upubsub * u, const char * topic, void * data, upubsub_listen_func * listen) {
  177. struct upubsub_listener l;
  178. l.topic = topic;
  179. l.data = data;
  180. l.listen = listen;
  181. return upubsub_subscribe_listener(u, l);
  182. }
  183. int upubsub_publish_message(struct upubsub u, struct upubsub_message m) {
  184. int sent = 0;
  185. size_t index;
  186. struct upubsub_listener * found = NULL;
  187. struct upubsub_listener key;
  188. struct bsearch_range_result ran;
  189. key.topic = m.topic;
  190. key.id = 1;
  191. ran = bsearch_range(&key, u.listeners, u.size, sizeof(struct upubsub_listener), upubsub_listener_cmp_topic);
  192. if (ran.high < ran.low) { /* Not found, bail out */
  193. return sent;
  194. }
  195. /* Broadcast to all in range. */
  196. for (index = ran.low; index <= ran.high; index ++) {
  197. found = u.listeners + index;
  198. found->listen(found, m);
  199. sent ++;
  200. }
  201. return sent;
  202. }
  203. struct upubsub_message upubsub_message_make(const char * topic, void * data, size_t size) {
  204. struct upubsub_message m = { 0 };
  205. m.size = size;
  206. if (m.size <= UPUBSUB_MESSAGE_SIZE) {
  207. memcpy(m.data.bytes, data, m.size);
  208. m.data_type = upubsub_message_data_type_bytes;
  209. } else {
  210. m.data.ptr = data;
  211. m.data_type = upubsub_message_data_type_ptr;
  212. }
  213. m.topic = topic;
  214. return m;
  215. }
  216. int upubsub_publish_data(struct upubsub u, const char * topic, void * data, size_t size) {
  217. struct upubsub_message m = upubsub_message_make(topic, data, size);
  218. return upubsub_publish_message(u, m);
  219. }
  220. int upubsub_publish_str(struct upubsub u, const char * topic, char * str) {
  221. return upubsub_publish_data(u, topic, str, strlen(str));
  222. }
  223. #ifdef UPUBSUB_MESSAGE_TYPE
  224. int upubsub_publish_type(struct upubsub u, const char * topic, UPUBSUB_MESSAGE_TYPE mt) {
  225. struct upubsub_message m = { 0 };
  226. m.size = sizeof(mt);
  227. m.data.typed = mt;
  228. m.data_type = upubsub_message_data_type_typed;
  229. m.topic = topic;
  230. return upubsub_publish_message(u, m);
  231. }
  232. #endif
  233. int upubsub_unsubscribe_listener(struct upubsub * u, struct upubsub_listener * l) {
  234. struct upubsub_listener * found = NULL;
  235. found = bsearch(l, u->listeners, u->size, sizeof(struct upubsub_listener), upubsub_listener_cmp_id);
  236. if (!found) {
  237. return -1;
  238. }
  239. /* Set to unused. */
  240. found->id = 0;
  241. found->topic = NULL;
  242. /* Unused will be sorted to the back. */
  243. qsort(u->listeners, u->size, sizeof(struct upubsub_listener), upubsub_listener_cmp_topic_id);
  244. u->size--;
  245. return 0;
  246. }
  247. int upubsub_unsubscribe(struct upubsub_listener * l) {
  248. return upubsub_unsubscribe_listener(l->upubsub, l);
  249. }
  250. #endif /* UPUBSUB_IMPLEMENTATION */
  251. #ifdef UPUBSUB_TEST
  252. #include <stdio.h>
  253. int listen_print_str(struct upubsub_listener *l, struct upubsub_message m) {
  254. printf("received message on topic %s: data %s, %s\n", l->topic,
  255. (char*)(l->data), (char *)(m.data.bytes));
  256. return 0;
  257. }
  258. int listen_print_typed_str(struct upubsub_listener *l, struct upubsub_message m) {
  259. printf("received typed message on topic %s: data %s, %s, %d, %d\n", l->topic,
  260. (char*)(l->data), (char *)(m.data.typed.text),
  261. m.data.typed.x, m.data.typed.y);
  262. return 0;
  263. }
  264. int compare_int(const void * key, const void * mem) {
  265. int * i1 = (int*)key;
  266. int * i2 = (int*)mem;
  267. return (*i1) - (*i2);
  268. }
  269. int main(void) {
  270. struct upubsub pusu = { 0 };
  271. struct upubsub_listener *l[10];
  272. int arr[] = { 7, 8 , 8, 9, 9, 9, 10, 11, 12, 20, 20, 32 };
  273. int low, high;
  274. int key = 9;
  275. struct bsearch_range_result ran;
  276. ran = bsearch_range(&key, arr, 12, sizeof(int), compare_int);
  277. printf("Low, high: %d -> %d...\n ", ran.low, ran.high);
  278. if ((ran.low >= 0) && (ran.low < 12)) {
  279. printf("Low: %d\n", arr[ran.low]);
  280. }
  281. if ((ran.high >= 0) && (ran.high < 12)) {
  282. printf("High: %d\n", arr[ran.high]);
  283. }
  284. for (key = 0; key < 40; key ++) {
  285. ran = bsearch_range(&key, arr, 12, sizeof(int), compare_int);
  286. printf("Key: %d; Low, high: %d -> %d:", key, ran.low, ran.high);
  287. if ((ran.low >= 0) && (ran.low < 12)) {
  288. printf(" Low: %d", arr[low]);
  289. }
  290. if ((ran.high >= 0) && (ran.high < 12)) {
  291. printf(" High: %d", arr[ran.high]);
  292. }
  293. printf("\n");
  294. }
  295. printf("Subscribing...\n");
  296. l[0] = upubsub_subscribe(&pusu, "POWER" , "SYST", listen_print_str);
  297. l[1] = upubsub_subscribe(&pusu, "POWER" , "APP1", listen_print_str);
  298. l[2] = upubsub_subscribe(&pusu, "SYSTEM", "APP2", listen_print_str);
  299. l[3] = upubsub_subscribe(&pusu, "SYSTEM", "WIND", listen_print_str);
  300. l[4] = upubsub_subscribe(&pusu, "SYSTEM", "APP3", listen_print_str);
  301. l[5] = upubsub_subscribe(&pusu, "POWER" , "APP4", listen_print_str);
  302. l[6] = upubsub_subscribe(&pusu, "TYPED" , "APP5", listen_print_typed_str);
  303. printf("Publishing...\n");
  304. upubsub_publish_str(pusu, "POWER", "DOWN");
  305. upubsub_publish_str(pusu, "POWER", "UP");
  306. upubsub_publish_str(pusu, "SYSTEM", "CLICK(7,8)");
  307. upubsub_publish_str(pusu, "SYSTEM", "ROLL(9,5)");
  308. upubsub_publish_str(pusu, "NULL", "IGNORE");
  309. {
  310. UPUBSUB_MESSAGE_TYPE typed = {0};
  311. strcpy(typed.text, "TYPED");
  312. typed.x = 10;
  313. typed.y = 20;
  314. upubsub_publish_type(pusu, "TYPED", typed);
  315. }
  316. printf("Removing topics...\n");
  317. upubsub_unsubscribe(l[1]);
  318. upubsub_unsubscribe(l[3]);
  319. printf("Publishing...\n");
  320. upubsub_publish_str(pusu, "POWER", "DOWN 2");
  321. upubsub_publish_str(pusu, "POWER", "UP 2");
  322. upubsub_publish_str(pusu, "SYSTEM", "CLICK(7,8) 2");
  323. upubsub_publish_str(pusu, "SYSTEM", "ROLL(9,5) 2");
  324. upubsub_publish_str(pusu, "NULL", "IGNORE");
  325. {
  326. UPUBSUB_MESSAGE_TYPE typed = {0};
  327. strcpy(typed.text, "TYPED");
  328. typed.x = 30;
  329. typed.y = 40;
  330. upubsub_publish_type(pusu, "TYPED", typed);
  331. }
  332. return 0;
  333. }
  334. #endif /* UPUBSUB_TEST */