| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include <assert.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include "hqueue.h" |
| |
| |
| typedef struct |
| { |
| ERL_NIF_TERM atom_ok; |
| ERL_NIF_TERM atom_error; |
| ERL_NIF_TERM atom_value; |
| ERL_NIF_TERM atom_empty; |
| ERL_NIF_TERM atom_full; |
| ERL_NIF_TERM atom_max_elems; |
| ERL_NIF_TERM atom_heap_size; |
| ERL_NIF_TERM atom_too_small; |
| ErlNifResourceType* res_hqueue; |
| } hqueue_priv; |
| |
| |
| typedef struct |
| { |
| ErlNifEnv* env; |
| ERL_NIF_TERM value; |
| } hqnode_nif_t; |
| |
| |
| typedef struct |
| { |
| int version; |
| uint64_t gen; |
| hqueue_t* hqueue; |
| ErlNifPid p; |
| } hqueue_nif_t; |
| |
| |
| static const uint32_t default_max_elems = UINT32_MAX-1; |
| static const uint32_t default_heap_size = 1024; |
| |
| |
| static inline ERL_NIF_TERM |
| make_atom(ErlNifEnv* env, const char* name) |
| { |
| ERL_NIF_TERM ret; |
| if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) { |
| return ret; |
| } |
| return enif_make_atom(env, name); |
| } |
| |
| |
| static inline ERL_NIF_TERM |
| make_ok(ErlNifEnv* env, hqueue_priv* priv, ERL_NIF_TERM value) |
| { |
| return enif_make_tuple2(env, priv->atom_ok, value); |
| } |
| |
| |
| static inline ERL_NIF_TERM |
| make_error(ErlNifEnv* env, hqueue_priv* priv, ERL_NIF_TERM reason) |
| { |
| return enif_make_tuple2(env, priv->atom_error, reason); |
| } |
| |
| |
| static inline int |
| check_pid(ErlNifEnv* env, hqueue_nif_t* hqueue_nif) |
| { |
| ErlNifPid pid; |
| enif_self(env, &pid); |
| |
| if(enif_compare(pid.pid, hqueue_nif->p.pid) == 0) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| |
| void |
| hqueue_nif_node_free(hqnode_nif_t* hqnode_nif) |
| { |
| enif_free_env(hqnode_nif->env); |
| enif_free(hqnode_nif); |
| |
| return; |
| } |
| |
| |
| void |
| hqueue_nif_node_free_ext(void* node) |
| { |
| hqueue_nif_node_free((hqnode_nif_t*) node); |
| |
| return; |
| } |
| |
| |
| hqnode_nif_t* |
| hqueue_nif_node_alloc() |
| { |
| hqnode_nif_t* node = (hqnode_nif_t*) enif_alloc(sizeof(hqnode_nif_t*)); |
| |
| memset(node, 0, sizeof(hqnode_nif_t)); |
| |
| node->env = enif_alloc_env(); |
| |
| return node; |
| } |
| |
| |
| static int |
| get_uint_param(ErlNifEnv* env, ERL_NIF_TERM value, ERL_NIF_TERM atom, uint32_t* p) |
| { |
| const ERL_NIF_TERM* tuple; |
| int arity; |
| |
| if(!enif_get_tuple(env, value, &arity, &tuple)) { |
| return 0; |
| } |
| |
| if(arity != 2) { |
| return 0; |
| } |
| |
| if(enif_compare(tuple[0], atom) != 0) { |
| return 0; |
| } |
| |
| if(!enif_get_uint(env, tuple[1], p)) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| |
| static inline hqueue_nif_t* |
| hqueue_nif_create_int(ErlNifEnv* env, hqueue_priv* priv, uint32_t max_elems, |
| uint32_t heap_size) |
| { |
| hqueue_nif_t* hqueue_nif = NULL; |
| |
| assert(priv != NULL && "missing private data member"); |
| |
| hqueue_nif = (hqueue_nif_t*) enif_alloc_resource( |
| priv->res_hqueue, sizeof(hqueue_nif_t)); |
| memset(hqueue_nif, 0, sizeof(hqueue_nif_t)); |
| hqueue_nif->version = HQ_VERSION; |
| |
| hqueue_nif->hqueue = hqueue_new(max_elems, heap_size); |
| |
| if(hqueue_nif->hqueue == NULL ) { |
| enif_release_resource(hqueue_nif); |
| return NULL; |
| } |
| |
| enif_self(env, &(hqueue_nif->p)); |
| |
| return hqueue_nif; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| ERL_NIF_TERM ret; |
| ERL_NIF_TERM opts; |
| ERL_NIF_TERM value; |
| uint32_t max_elems = default_max_elems; |
| uint32_t heap_size = default_heap_size; |
| |
| if(argc != 1) { |
| return enif_make_badarg(env); |
| } |
| |
| opts = argv[0]; |
| if(!enif_is_list(env, opts)) { |
| return enif_make_badarg(env); |
| } |
| |
| while(enif_get_list_cell(env, opts, &value, &opts)) { |
| if(get_uint_param(env, value, priv->atom_max_elems, &max_elems)) { |
| continue; |
| } else if(get_uint_param(env, value, priv->atom_heap_size, &heap_size)) { |
| continue; |
| } else { |
| return enif_make_badarg(env); |
| } |
| } |
| |
| hqueue_nif = hqueue_nif_create_int(env, priv, max_elems, heap_size); |
| if(hqueue_nif == NULL) { |
| return enif_make_badarg(env); |
| } |
| |
| ret = enif_make_resource(env, hqueue_nif); |
| enif_release_resource(hqueue_nif); |
| |
| return make_ok(env, priv, ret); |
| } |
| |
| |
| static void |
| hqueue_nif_free(ErlNifEnv* env, void* obj) |
| { |
| hqueue_nif_t* hqueue_nif = (hqueue_nif_t*) obj; |
| |
| hqueue_free2(hqueue_nif->hqueue, hqueue_nif_node_free_ext); |
| |
| return; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_extract_max(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| hqnode_nif_t* hqnode_nif; |
| double tmp_priority; |
| ERL_NIF_TERM ret; |
| ERL_NIF_TERM priority; |
| ERL_NIF_TERM value; |
| |
| if(argc != 1) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if (!hqueue_extract_max(hqueue_nif->hqueue, &tmp_priority, (void**) &hqnode_nif)) { |
| return make_error(env, priv, priv->atom_empty); |
| } |
| |
| priority = enif_make_double(env, tmp_priority); |
| value = enif_make_copy(env, hqnode_nif->value); |
| ret = enif_make_tuple2(env, priority, value); |
| |
| hqueue_nif_node_free(hqnode_nif); |
| |
| return ret; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_insert(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| hqnode_nif_t* hqnode_nif; |
| ERL_NIF_TERM ret; |
| double priority; |
| |
| if(argc != 3) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_double(env, argv[1], &priority)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(priority < 0.0) { |
| return enif_make_badarg(env); |
| } |
| |
| hqnode_nif = hqueue_nif_node_alloc(); |
| hqnode_nif->value = enif_make_copy(hqnode_nif->env, argv[2]); |
| |
| if (!hqueue_insert(hqueue_nif->hqueue, priority, (void*) hqnode_nif)) { |
| return make_error(env, priv, priv->atom_full); |
| } |
| |
| ret = priv->atom_ok; |
| |
| return ret; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| ERL_NIF_TERM ret; |
| |
| if(argc != 1) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| ret = enif_make_uint64(env, hqueue_size(hqueue_nif->hqueue)); |
| |
| return ret; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_heap_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| ERL_NIF_TERM ret; |
| |
| if(argc != 1) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| ret = enif_make_uint64(env, hqueue_heap_size(hqueue_nif->hqueue)); |
| |
| return ret; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_max_elems(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| ERL_NIF_TERM ret; |
| |
| if(argc != 1) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| ret = enif_make_uint64(env, hqueue_max_elems(hqueue_nif->hqueue)); |
| |
| return ret; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_to_list(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| hqueue_t* hqueue; |
| hqnode_nif_t* hqnode_nif; |
| double tmp_priority; |
| ERL_NIF_TERM ret = enif_make_list(env, 0); |
| ERL_NIF_TERM priority; |
| ERL_NIF_TERM value; |
| ERL_NIF_TERM tuple; |
| uint32_t i; |
| |
| if(argc != 1) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| hqueue = hqueue_nif->hqueue; |
| |
| for (i = 1; i <= hqueue_size(hqueue); i++) { |
| hqueue_get_elem(hqueue, i, &tmp_priority, (void **) &hqnode_nif); |
| priority = enif_make_double(env, tmp_priority); |
| value = enif_make_copy(env, hqnode_nif->value); |
| tuple = enif_make_tuple2(env, priority, value); |
| ret = enif_make_list_cell(env, tuple, ret); |
| } |
| |
| return ret; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_scale_by(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| ERL_NIF_TERM ret; |
| double factor; |
| |
| if(argc != 2) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_double(env, argv[1], &factor)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(factor < 0.0) { |
| return enif_make_badarg(env); |
| } |
| |
| hqueue_scale_by(hqueue_nif->hqueue, factor); |
| |
| ret = priv->atom_ok; |
| |
| return ret; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_resize_heap(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| ERL_NIF_TERM ret; |
| uint32_t new_heap_size; |
| uint32_t old_heap_size; |
| |
| if(argc != 2) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_uint(env, argv[1], &new_heap_size)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(hqueue_size(hqueue_nif->hqueue) > new_heap_size) { |
| return make_error(env, priv, priv->atom_too_small); |
| } |
| |
| if((old_heap_size = hqueue_resize_heap(hqueue_nif->hqueue, new_heap_size)) == 0) { |
| return enif_make_badarg(env); |
| } |
| |
| ret = enif_make_uint64(env, old_heap_size); |
| |
| return ret; |
| } |
| |
| |
| static ERL_NIF_TERM |
| hqueue_nif_set_max_elems(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| hqueue_priv* priv = enif_priv_data(env); |
| hqueue_nif_t* hqueue_nif; |
| ERL_NIF_TERM ret; |
| uint32_t new_max_elems; |
| uint32_t old_max_elems; |
| |
| if(argc != 2) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, hqueue_nif)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!enif_get_uint(env, argv[1], &new_max_elems)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(hqueue_size(hqueue_nif->hqueue) > new_max_elems) { |
| return make_error(env, priv, priv->atom_too_small); |
| } |
| |
| if ((old_max_elems = hqueue_set_max_elems(hqueue_nif->hqueue, new_max_elems)) == 0) { |
| return enif_make_badarg(env); |
| } |
| |
| ret = enif_make_uint64(env, old_max_elems); |
| |
| return ret; |
| } |
| |
| |
| static int |
| load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) |
| { |
| int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; |
| ErlNifResourceType* res; |
| |
| hqueue_priv* new_priv = (hqueue_priv*) enif_alloc(sizeof(hqueue_priv)); |
| if(new_priv == NULL) { |
| return 1; |
| } |
| |
| res = enif_open_resource_type( |
| env, NULL, "hqueue", hqueue_nif_free, flags, NULL); |
| if(res == NULL) { |
| enif_free(new_priv); |
| return 1; |
| } |
| new_priv->res_hqueue = res; |
| |
| new_priv->atom_ok = make_atom(env, "ok"); |
| new_priv->atom_error = make_atom(env, "error"); |
| new_priv->atom_value = make_atom(env, "value"); |
| new_priv->atom_empty = make_atom(env, "empty"); |
| new_priv->atom_full = make_atom(env, "full"); |
| new_priv->atom_max_elems = make_atom(env, "max_elems"); |
| new_priv->atom_heap_size = make_atom(env, "heap_size"); |
| new_priv->atom_too_small = make_atom(env, "too_small"); |
| |
| *priv = (void*) new_priv; |
| |
| return 0; |
| } |
| |
| |
| static int |
| upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) |
| { |
| return load(env, priv, info); |
| } |
| |
| |
| static void |
| unload(ErlNifEnv* env, void* priv) |
| { |
| enif_free(priv); |
| return; |
| } |
| |
| |
| static ErlNifFunc funcs[] = { |
| {"new", 1, hqueue_nif_new}, |
| {"extract_max", 1, hqueue_nif_extract_max}, |
| {"insert", 3, hqueue_nif_insert}, |
| {"size", 1, hqueue_nif_size}, |
| {"heap_size", 1, hqueue_nif_heap_size}, |
| {"max_elems", 1, hqueue_nif_max_elems}, |
| {"set_max_elems", 2, hqueue_nif_set_max_elems}, |
| {"to_list", 1, hqueue_nif_to_list}, |
| {"scale_by", 2, hqueue_nif_scale_by}, |
| {"resize_heap", 2, hqueue_nif_resize_heap} |
| }; |
| |
| |
| ERL_NIF_INIT(hqueue, funcs, &load, NULL, &upgrade, &unload); |