/ src / core / registration.c
registration.c
  1  // SPDX-FileCopyrightText: 2023-2025 Le'Sec Core collective
  2  //
  3  // SPDX-License-Identifier: LGPL-3.0-or-later
  4  
  5  #include <time.h>
  6  #include <stdio.h>
  7  #include <stdlib.h>
  8  #include <stdarg.h>
  9  #include <string.h>
 10  #include <lscore/registration.h>
 11  #include "hashmap.h"
 12  #include "local.h"
 13  
 14  // We use some compiler trickery to generic types, especially for function
 15  // signatures, as the exact type of their arguments don't really matter much
 16  // here.  Types are sufficiently vague in the right places to shut the compiler
 17  // up, and sufficiently exact in in the other right places to make the compiler
 18  // happy.  All we have to do is to ensure that the function pointers are picked
 19  // from the correct table, where that matters.
 20  LSC_DATA_SET_TYPES(void);
 21  LSC_REGISTRATION_CALLBACK_TYPES(void);
 22  
 23  typedef struct lsc_implementation_bag_data_st {
 24    LSC_env_t *env;
 25    char *class;
 26    struct hashmap *table;
 27  } lsc_implementation_bag_data_t;
 28  
 29  typedef struct lsc_implementation_data_st {
 30    const LSC_void_dispatch_fn *dispatch;
 31    const LSC_void_destroy_fn *destroy;
 32    const void *dispatch_data;
 33    const char *docstring;
 34    const LSC_plugin_t *plugin;
 35    const lsc_implementation_bag_data_t *parent;
 36    // The identity is the actual hashing input
 37    const char *id;
 38  } lsc_implementation_data_t;
 39  
 40  static uint64_t hash_implementation_data(const void *item,
 41                                           uint64_t seed0, uint64_t seed1)
 42  {
 43    const lsc_implementation_data_t *data = item;
 44  
 45    return hashmap_xxhash3(data->id, strlen(data->id), seed0, seed1);
 46  }
 47  
 48  static int compare_implementation_data(const void *a, const void *b,
 49                                         void *udata)
 50  {
 51    const lsc_implementation_data_t *adata = a;
 52    const lsc_implementation_data_t *bdata = b;
 53  
 54    return strcmp(adata->id, bdata->id);
 55  }
 56  
 57  static LE_STATUS
 58  lsc_implementation_bag_dispatch(LSC_implementation_bag_t *t, int num, ...)
 59  {
 60    LE_STATUS status = LE_STS_ERROR; // TODO: Set more precise status
 61    va_list ap;
 62  
 63    va_start(ap, num);
 64    switch (num) {
 65    case LSC_NR_register_implementation:
 66    case LSC_NR_deregister_implementation:
 67      {
 68        const LSC_plugin_t *plugin = va_arg(ap, const LSC_plugin_t *);
 69        const char *id = va_arg(ap, const char *);
 70        const LSC_void_dispatch_fn *dispatch
 71          = va_arg(ap, const LSC_void_dispatch_fn *);
 72        const LSC_void_destroy_fn *destroy
 73          = va_arg(ap, const LSC_void_destroy_fn *);
 74        const void *dispatch_data = va_arg(ap, const void *);
 75        const char *docstring = va_arg(ap, const char *);
 76        lsc_implementation_data_t item;
 77        const lsc_implementation_data_t *pitem;
 78        lsc_implementation_bag_data_t *t2 = t->lsc_data;
 79  
 80        if (id == NULL || dispatch == NULL || destroy == NULL)
 81          break;                  // TODO: Set more precise status
 82  
 83        item.id = id;
 84        item.plugin = plugin;
 85        item.dispatch = dispatch;
 86        item.destroy = destroy;
 87        item.dispatch_data = dispatch_data;
 88        item.docstring = docstring;
 89        item.parent = t2;
 90  
 91        switch (num) {
 92        case LSC_NR_register_implementation:
 93          {
 94            if ((pitem = hashmap_get(t2->table, &item)) == NULL) {
 95              if (hashmap_set(t2->table, &item) != NULL)
 96                // What the hell, there was an item after all?  TODO: MEMLEAK
 97                break;                // TODO: Set more precise status
 98            }
 99          }
100          status = LE_STS_SUCCESS;
101          break;
102        case LSC_NR_deregister_implementation:
103          hashmap_delete(t2->table, &item);
104          status = LE_STS_SUCCESS;
105          break;
106        }
107      }
108      break;
109    case LSC_NR_find_implementation:
110      {
111        const char *id = va_arg(ap, const char *);
112        const LSC_plugin_t **plugin = va_arg(ap, const LSC_plugin_t **);
113        const LSC_void_dispatch_fn **dispatch
114          = va_arg(ap, const LSC_void_dispatch_fn **);
115        const LSC_void_destroy_fn **destroy
116          = va_arg(ap, const LSC_void_destroy_fn **);
117        const void **dispatch_data = va_arg(ap, const void **);
118        const char **docstring = va_arg(ap, const char **);
119        lsc_implementation_data_t item;
120        const lsc_implementation_data_t *pitem;
121        lsc_implementation_bag_data_t *t2 = t->lsc_data;
122  
123        item.id = id;
124        if ((pitem = hashmap_get(t2->table, &item)) != NULL) {
125          *dispatch = pitem->dispatch;
126          *destroy = pitem->destroy;
127          *dispatch_data = pitem->dispatch_data;
128          *docstring = pitem->docstring;
129          if (plugin != NULL)
130            *plugin = pitem->plugin;
131          status = LE_STS_SUCCESS;
132        }
133      }
134      break;
135    case LSC_NR_do_all_implementations:
136      {
137        LSC_do_all_void_implementations_callback_fn *fn
138          = va_arg(ap, LSC_do_all_void_implementations_callback_fn *);
139        void *user_arg = va_arg(ap, void *);
140        const lsc_implementation_data_t *pitem = NULL;
141        lsc_implementation_bag_data_t *t2 = t->lsc_data;
142        size_t cursor;
143  
144        for (cursor = 0; hashmap_iter(t2->table, &cursor, (void **)&pitem);)
145          if (!LE_status_is_OK(status = fn(t2->class, pitem->id,
146                                           pitem->plugin, t2->env,
147                                           pitem->dispatch, pitem->destroy,
148                                           pitem->dispatch_data,
149                                           pitem->docstring, user_arg)))
150            break;
151      }
152      break;
153    }
154  
155    va_end(ap);
156    return status;
157  }
158  
159  static LE_STATUS
160  lsc_destroy_implementation_bag(LSC_implementation_bag_t *t)
161  {
162    if (t == NULL)
163      return LE_STS_SUCCESS;
164    if (t->lsc_data != NULL) {
165      lsc_implementation_bag_data_t *t2 = t->lsc_data;
166      hashmap_free(t2->table);
167      free(t2->class);
168    }
169    free(t);
170    return LE_STS_SUCCESS;
171  }
172  
173  // This is called from the environment itself to get this bad to appear
174  LE_STATUS
175  lsc_get_environment_implementation_bag(LSC_env_t *env, const char *class,
176                                         LSC_implementation_bag_t **t)
177  {
178    LE_STATUS sts;
179    int seed = time(NULL);
180  
181    sts = LSC_find_environment_bag(env, class, (LSC_env_bag_t **)t);
182    if (LE_status_is_warning(sts)) {
183      size_t table_data_size
184        = sizeof(**t) + sizeof(lsc_implementation_bag_data_t);
185      if ((*t = malloc(table_data_size)) == NULL)
186        return LE_STS_ERROR;
187      lsc_implementation_bag_data_t *t2 = (void *)*t + sizeof(**t);
188      t2->table = hashmap_new(sizeof(lsc_implementation_data_t), 0,
189                              seed, seed,
190                              hash_implementation_data,
191                              compare_implementation_data,
192                              NULL, NULL);
193      t2->class = strdup(class);
194      t2->env = env;
195      (*t)->lsc_dispatch = lsc_implementation_bag_dispatch;
196      (*t)->lsc_destroy = lsc_destroy_implementation_bag;
197      (*t)->lsc_data = t2;
198      if (t2->table == NULL || t2->class == NULL)
199        sts = LE_STS_ERROR;
200      else
201        sts = LSC_add_environment_bag(env, class, *(LSC_env_bag_t **)t);
202      if (!LE_status_is_OK(sts)) {
203        (*t)->lsc_destroy(*t);
204        *t = NULL;
205        return LE_STS_ERROR;
206      }
207    }
208    return LE_STS_SUCCESS;
209  }
210  
211  #ifdef TEST_REGISTRATION
212  
213  #undef LSC_NAME
214  #define LSC_NAME(x) TEST_##x
215  LSC_DATA_SET_TYPES(something);
216  LSC_REGISTRATION_FUNCTIONS("TEST::", something);
217  
218  const char LSC_NAME(something_docs)[] = "Implementation of something";
219  
220  static LE_STATUS LSC_NAME(something_dispatch)(LSC_NAME(something_t) *data, int num, ...) {}
221  static LE_STATUS LSC_NAME(something_destroy)(LSC_NAME(something_t) *data) {}
222  static LE_STATUS LSC_NAME(something_print)
223       (const char *class, const char *id,
224        const LSC_plugin_t *plugin, const LSC_env_t *env,
225        LSC_NAME(something_dispatch_fn) *dispatch,
226        LSC_NAME(something_destroy_fn) *destroy,
227        const void *dispatch_data,
228        const char *docstring,
229        void *user_arg)
230  {
231    printf("FOUND %s : %s", class, id);
232    printf(" [");
233    if (plugin)
234      printf("plugin: %p", (void *)plugin);
235    else
236      printf("built in", (void *)plugin);
237    if (env)
238      printf(", env: %p]", (void *)env);
239    else
240      printf(", env-independent]");
241    printf("]\n");
242    if (docstring)
243      printf("--BEGIN DOCS--\n%s\n--END DOCS--\n", docstring);
244    return LE_STS_SUCCESS;
245  }
246  #undef LSC_NAME
247  #define LSC_NAME(x)                    LSC_STD_NAME(x)
248  
249  int main()
250  {
251    LSC_env_t *env = NULL;
252    TEST_something_dispatch_fn *dispatch;
253    TEST_something_destroy_fn *destroy;
254    const void *dispatch_data;
255    const char *docstring;
256    LE_STATUS sts;
257    int e = 0;
258  
259    // Setup
260    if (!LE_status_is_OK(sts = LSC_new_environment(&env)))
261      e++;
262  
263    // Add an implementation
264    sts = TEST_register_something_implementation(env, NULL, "something1",
265                                                 TEST_something_dispatch,
266                                                 TEST_something_destroy,
267                                                 NULL,
268                                                 TEST_something_docs);
269    if (!LE_status_is_OK(sts))
270      e++;
271  
272    // Find the same implementation, and check it
273    sts = TEST_find_something_implementation(env, "something1", NULL,
274                                             &dispatch,
275                                             &destroy,
276                                             &dispatch_data,
277                                             &docstring);
278    if (!LE_status_is_OK(sts)
279        || dispatch != TEST_something_dispatch
280        || destroy != TEST_something_destroy
281        || docstring != TEST_something_docs)
282      e++;
283  
284    // Try printing
285    sts = TEST_do_all_something_implementations(env, TEST_something_print,
286                                                NULL);
287    if (!LE_status_is_OK(sts))
288      e++;
289  
290    // Cleanup
291    if (!LE_status_is_OK(sts = LSC_free_environment(env)))
292      e++;
293  
294    exit(e);
295  }
296  
297  #endif