|
|
@ -1,56 +1,22 @@ |
|
|
|
#include "erl_nif.h"
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "erl_nif.h"
|
|
|
|
#include "concurrentqueue.h"
|
|
|
|
|
|
|
|
struct atoms { |
|
|
|
ERL_NIF_TERM atomOk; |
|
|
|
ERL_NIF_TERM atomError; |
|
|
|
ERL_NIF_TERM atomTrue; |
|
|
|
//ERL_NIF_TERM atomFalse;
|
|
|
|
//ERL_NIF_TERM atomUndefined;
|
|
|
|
ERL_NIF_TERM atomEmpty; |
|
|
|
}; |
|
|
|
|
|
|
|
struct shared_data { |
|
|
|
ErlNifResourceType *resQueueInstance; |
|
|
|
}; |
|
|
|
|
|
|
|
struct q_item { |
|
|
|
struct qData { |
|
|
|
ErlNifEnv *env; |
|
|
|
ERL_NIF_TERM term; |
|
|
|
}; |
|
|
|
|
|
|
|
struct squeue { |
|
|
|
moodycamel::ConcurrentQueue<q_item> *queue; |
|
|
|
struct lfqIns { |
|
|
|
moodycamel::ConcurrentQueue<qData> *LFQ; |
|
|
|
moodycamel::ConcurrentQueue<ErlNifEnv *> *G_ENV; |
|
|
|
}; |
|
|
|
|
|
|
|
const char kAtomOk[] = "ok"; |
|
|
|
const char kAtomError[] = "error"; |
|
|
|
const char kAtomTrue[] = "true"; |
|
|
|
//const char kAtomFalse[] = "false";
|
|
|
|
//const char kAtomUndefined[] = "undefined";
|
|
|
|
const char kAtomEmpty[] = "empty"; |
|
|
|
|
|
|
|
atoms ATOMS; |
|
|
|
|
|
|
|
void nif_enlfq_free(ErlNifEnv *env, void *obj); |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_enlfq_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_enlfq_push(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_enlfq_pop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); |
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
ERL_NIF_TERM atomOk; |
|
|
|
ERL_NIF_TERM atomError; |
|
|
|
ERL_NIF_TERM atomTrue; |
|
|
|
ERL_NIF_TERM atomFalse; |
|
|
|
ERL_NIF_TERM atomEmpty; |
|
|
|
|
|
|
|
ERL_NIF_TERM make_binary(ErlNifEnv *env, const char *buff, size_t length) { |
|
|
|
ERL_NIF_TERM term; |
|
|
@ -60,183 +26,238 @@ ERL_NIF_TERM make_binary(ErlNifEnv *env, const char *buff, size_t length) { |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM make_error(ErlNifEnv *env, const char *error) { |
|
|
|
return enif_make_tuple2(env, ATOMS.atomError, make_binary(env, error, strlen(error))); |
|
|
|
return enif_make_tuple2(env, atomError, make_binary(env, error, strlen(error))); |
|
|
|
} |
|
|
|
|
|
|
|
void eLfqFree(ErlNifEnv *, void *obj) { |
|
|
|
lfqIns *ObjIns = static_cast<lfqIns *>(obj); |
|
|
|
|
|
|
|
void open_resources(ErlNifEnv *env, shared_data *data) { |
|
|
|
ErlNifResourceFlags flags = static_cast<ErlNifResourceFlags>(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); |
|
|
|
data->resQueueInstance = enif_open_resource_type(env, NULL, "enlfq_instance", nif_enlfq_free, flags, NULL); |
|
|
|
} |
|
|
|
|
|
|
|
int on_nif_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM) { |
|
|
|
if (ObjIns != nullptr) { |
|
|
|
qData Data; |
|
|
|
while (ObjIns->LFQ->try_dequeue(Data)) { |
|
|
|
enif_free_env(Data.env); |
|
|
|
} |
|
|
|
|
|
|
|
ATOMS.atomOk = make_atom(env, kAtomOk); |
|
|
|
ATOMS.atomError = make_atom(env, kAtomError); |
|
|
|
ATOMS.atomTrue = make_atom(env, kAtomTrue); |
|
|
|
// ATOMS.atomFalse = make_atom(env, kAtomFalse);
|
|
|
|
// ATOMS.atomUndefined = make_atom(env, kAtomUndefined);
|
|
|
|
ATOMS.atomEmpty = make_atom(env, kAtomEmpty); |
|
|
|
ErlNifEnv *StoreEnv; |
|
|
|
while (ObjIns->G_ENV->try_dequeue(StoreEnv)) { |
|
|
|
enif_free_env(StoreEnv); |
|
|
|
} |
|
|
|
delete ObjIns->LFQ; |
|
|
|
delete ObjIns->G_ENV; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
shared_data *data = static_cast<shared_data *>(enif_alloc(sizeof(shared_data))); |
|
|
|
open_resources(env, data); |
|
|
|
int nifLoad(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM) { |
|
|
|
ErlNifResourceFlags flags = static_cast<ErlNifResourceFlags>(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); |
|
|
|
ErlNifResourceType *ResIns; |
|
|
|
ResIns = enif_open_resource_type(env, NULL, "eLfqRes", eLfqFree, flags, NULL); |
|
|
|
if (NULL == ResIns) |
|
|
|
return -1; |
|
|
|
|
|
|
|
*priv_data = ResIns; |
|
|
|
atomOk = enif_make_atom(env, "ok"); |
|
|
|
atomError = enif_make_atom(env, "error"); |
|
|
|
atomTrue = enif_make_atom(env, "true"); |
|
|
|
atomFalse = enif_make_atom(env, "false"); |
|
|
|
atomEmpty = enif_make_atom(env, "empty"); |
|
|
|
|
|
|
|
*priv_data = data; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
void on_nif_unload(ErlNifEnv *, void *priv_data) { |
|
|
|
shared_data *data = static_cast<shared_data *>(priv_data); |
|
|
|
enif_free(data); |
|
|
|
} |
|
|
|
|
|
|
|
int on_nif_upgrade(ErlNifEnv *env, void **priv, void **, ERL_NIF_TERM) { |
|
|
|
shared_data *data = static_cast<shared_data *>(enif_alloc(sizeof(shared_data))); |
|
|
|
open_resources(env, data); |
|
|
|
int nifUpgrade(ErlNifEnv *env, void **priv_data, void **, ERL_NIF_TERM) { |
|
|
|
ErlNifResourceFlags flags = static_cast<ErlNifResourceFlags>(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); |
|
|
|
ErlNifResourceType *ResIns; |
|
|
|
ResIns = enif_open_resource_type(env, NULL, "eLfqRes", eLfqFree, flags, NULL); |
|
|
|
if (NULL == ResIns) |
|
|
|
return -1; |
|
|
|
|
|
|
|
*priv = data; |
|
|
|
*priv_data = ResIns; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
void nif_enlfq_free(ErlNifEnv *, void *obj) { |
|
|
|
squeue *inst = static_cast<squeue *>(obj); |
|
|
|
|
|
|
|
if (inst != nullptr) { |
|
|
|
q_item item; |
|
|
|
while (inst->queue->try_dequeue(item)) { |
|
|
|
enif_free_env(item.env); |
|
|
|
} |
|
|
|
delete inst->queue; |
|
|
|
} |
|
|
|
void nifUnload(ErlNifEnv *, void *priv_data) { |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_new(ErlNifEnv *env, int, const ERL_NIF_TERM *) { |
|
|
|
shared_data *data = static_cast<shared_data *>(enif_priv_data(env)); |
|
|
|
ERL_NIF_TERM nifNew(ErlNifEnv *env, int, const ERL_NIF_TERM *) { |
|
|
|
ErlNifResourceType *ResIns = static_cast<ErlNifResourceType *>(enif_priv_data(env)); |
|
|
|
|
|
|
|
lfqIns *ObjIns = static_cast<lfqIns *>(enif_alloc_resource(ResIns, sizeof(lfqIns))); |
|
|
|
ObjIns->LFQ = new moodycamel::ConcurrentQueue<qData>; |
|
|
|
ObjIns->G_ENV = new moodycamel::ConcurrentQueue<ErlNifEnv *>; |
|
|
|
|
|
|
|
squeue *qinst = static_cast<squeue *>(enif_alloc_resource(data->resQueueInstance, sizeof(squeue))); |
|
|
|
qinst->queue = new moodycamel::ConcurrentQueue<q_item>; |
|
|
|
|
|
|
|
if (qinst == NULL) |
|
|
|
if (ObjIns == NULL) |
|
|
|
return make_error(env, "enif_alloc_resource failed"); |
|
|
|
|
|
|
|
ERL_NIF_TERM term = enif_make_resource(env, qinst); |
|
|
|
enif_release_resource(qinst); |
|
|
|
return enif_make_tuple2(env, ATOMS.atomOk, term); |
|
|
|
ERL_NIF_TERM term = enif_make_resource(env, ObjIns); |
|
|
|
enif_release_resource(ResIns); |
|
|
|
return enif_make_tuple2(env, atomOk, term); |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_in2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
shared_data *data = static_cast<shared_data *>(enif_priv_data(env)); |
|
|
|
ERL_NIF_TERM nifIn2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
ErlNifResourceType *ResIns = static_cast<ErlNifResourceType *>(enif_priv_data(env)); |
|
|
|
|
|
|
|
squeue *inst; |
|
|
|
lfqIns *ObjIns = NULL; |
|
|
|
|
|
|
|
if (!enif_get_resource(env, argv[0], data->resQueueInstance, (void **) &inst)) { |
|
|
|
if (!enif_get_resource(env, argv[0], ResIns, (void **) &ObjIns)) { |
|
|
|
return enif_make_badarg(env); |
|
|
|
} |
|
|
|
|
|
|
|
q_item item; |
|
|
|
|
|
|
|
item.env = enif_alloc_env(); |
|
|
|
item.term = enif_make_copy(item.env, argv[1]); |
|
|
|
qData InTerm; |
|
|
|
|
|
|
|
inst->queue->enqueue(item); |
|
|
|
InTerm.env = enif_alloc_env(); |
|
|
|
InTerm.term = enif_make_copy(InTerm.env, argv[1]); |
|
|
|
|
|
|
|
return ATOMS.atomTrue; |
|
|
|
if (ObjIns->LFQ->enqueue(InTerm)){ |
|
|
|
return atomTrue; |
|
|
|
} else { |
|
|
|
return atomFalse; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_in3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomTrue; |
|
|
|
// ERL_NIF_TERM nifIn2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) {
|
|
|
|
// ErlNifResourceType* ResIns = static_cast<ErlNifResourceType *>(enif_priv_data(env));
|
|
|
|
//
|
|
|
|
// lfqIns *ObjIns = NULL;
|
|
|
|
//
|
|
|
|
// if (!enif_get_resource(env, argv[0], ResIns, (void **) &ObjIns)) {
|
|
|
|
// return enif_make_badarg(env);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// qData InTerm;
|
|
|
|
// if (ObjIns->G_ENV->try_dequeue(InTerm.env) != true) {
|
|
|
|
// InTerm.env = enif_alloc_env();
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// InTerm.term = enif_make_copy(InTerm.env, argv[1]);
|
|
|
|
//
|
|
|
|
// if (ObjIns->LFQ->enqueue(InTerm)){
|
|
|
|
// return atomTrue;
|
|
|
|
// } else {
|
|
|
|
// return atomFalse;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
ERL_NIF_TERM nifIn3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomTrue; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_ins2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomTrue; |
|
|
|
ERL_NIF_TERM nifIns2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomTrue; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_ins3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomTrue; |
|
|
|
ERL_NIF_TERM nifIns3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomTrue; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryIn2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomTrue; |
|
|
|
ERL_NIF_TERM nifTryIn2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomTrue; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryIn3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomTrue; |
|
|
|
ERL_NIF_TERM nifTryIn3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomTrue; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryIns2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomTrue; |
|
|
|
ERL_NIF_TERM nifTryIns2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomTrue; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryIns3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomTrue; |
|
|
|
ERL_NIF_TERM nifTryIns3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomTrue; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryOut1(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
shared_data *data = static_cast<shared_data *>(enif_priv_data(env)); |
|
|
|
squeue *inst = NULL; |
|
|
|
ERL_NIF_TERM nifTryOut1(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
ErlNifResourceType *ResIns = static_cast<ErlNifResourceType *>(enif_priv_data(env)); |
|
|
|
lfqIns *ObjIns = NULL; |
|
|
|
|
|
|
|
if (!enif_get_resource(env, argv[0], data->resQueueInstance, (void **) &inst)) { |
|
|
|
if (!enif_get_resource(env, argv[0], ResIns, (void **) &ObjIns)) { |
|
|
|
return enif_make_badarg(env); |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM term; |
|
|
|
q_item item; |
|
|
|
ERL_NIF_TERM OutTerm; |
|
|
|
qData Data; |
|
|
|
|
|
|
|
if (inst->queue->try_dequeue(item)) { |
|
|
|
term = enif_make_copy(env, item.term); |
|
|
|
enif_free_env(item.env); |
|
|
|
return enif_make_tuple2(env, ATOMS.atomOk, term); |
|
|
|
if (ObjIns->LFQ->try_dequeue(Data)) { |
|
|
|
OutTerm = enif_make_copy(env, Data.term); |
|
|
|
enif_free_env(Data.env); |
|
|
|
return enif_make_tuple2(env, atomOk, OutTerm); |
|
|
|
} else { |
|
|
|
return ATOMS.atomEmpty; |
|
|
|
return atomEmpty; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryOut2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomEmpty; |
|
|
|
// ERL_NIF_TERM nifTryOut1(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) {
|
|
|
|
// ErlNifResourceType* ResIns = static_cast<ErlNifResourceType *>(enif_priv_data(env));
|
|
|
|
// lfqIns *ObjIns = NULL;
|
|
|
|
//
|
|
|
|
// if (!enif_get_resource(env, argv[0], ResIns, (void **) &ObjIns)) {
|
|
|
|
// return enif_make_badarg(env);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// ERL_NIF_TERM OutTerm;
|
|
|
|
// qData Data;
|
|
|
|
//
|
|
|
|
// if (ObjIns->LFQ->try_dequeue(Data)) {
|
|
|
|
// OutTerm = enif_make_copy(env, Data.term);
|
|
|
|
//
|
|
|
|
// if(ObjIns->G_ENV->size_approx() > 1000){
|
|
|
|
// enif_free_env(Data.env);
|
|
|
|
// }else{
|
|
|
|
// enif_clear_env(Data.env);
|
|
|
|
// if (!ObjIns->G_ENV->enqueue(Data.env)){
|
|
|
|
// enif_free_env(Data.env);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// return enif_make_tuple2(env, atomOk, OutTerm);
|
|
|
|
// } else {
|
|
|
|
// return atomEmpty;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
ERL_NIF_TERM nifTryOut2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomEmpty; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryOuts2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomEmpty; |
|
|
|
ERL_NIF_TERM nifTryOuts2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomEmpty; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryOuts3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomEmpty; |
|
|
|
ERL_NIF_TERM nifTryOuts3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomEmpty; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryOutByProd2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomEmpty; |
|
|
|
ERL_NIF_TERM nifTryOutByProd2(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomEmpty; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_tryOutsByProd3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomEmpty; |
|
|
|
ERL_NIF_TERM nifTryOutByProd3(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomEmpty; |
|
|
|
} |
|
|
|
|
|
|
|
ERL_NIF_TERM nif_size1(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return ATOMS.atomEmpty; |
|
|
|
ERL_NIF_TERM nifSize1(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { |
|
|
|
return atomEmpty; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static ErlNifFunc nif_funcs[] = |
|
|
|
static ErlNifFunc nifFuncs[] = |
|
|
|
{ |
|
|
|
{"new", 0, nif_new}, |
|
|
|
{"in", 2, nif_in2}, |
|
|
|
{"in", 3, nif_in3}, |
|
|
|
{"ins", 2, nif_ins2}, |
|
|
|
{"ins", 3, nif_ins3}, |
|
|
|
{"tryIn", 2, nif_tryIn2}, |
|
|
|
{"tryIn", 3, nif_tryIn3}, |
|
|
|
{"tryIns", 2, nif_tryIns2}, |
|
|
|
{"tryIns", 3, nif_tryIns3}, |
|
|
|
{"tryOut", 1, nif_tryOut1}, |
|
|
|
{"tryOut", 2, nif_tryOut2}, |
|
|
|
{"tryOuts", 2, nif_tryOuts2}, |
|
|
|
{"tryOuts", 3, nif_tryOuts3}, |
|
|
|
{"tryOutByProd", 2, nif_tryOutByProd2}, |
|
|
|
{"tryOutsByProd", 3, nif_tryOutsByProd3}, |
|
|
|
{"size", 1, nif_size1} |
|
|
|
{"new", 0, nifNew}, |
|
|
|
{"in", 2, nifIn2}, |
|
|
|
{"in", 3, nifIn3}, |
|
|
|
{"ins", 2, nifIns2}, |
|
|
|
{"ins", 3, nifIns3}, |
|
|
|
{"tryIn", 2, nifTryIn2}, |
|
|
|
{"tryIn", 3, nifTryIn3}, |
|
|
|
{"tryIns", 2, nifTryIns2}, |
|
|
|
{"tryIns", 3, nifTryIns3}, |
|
|
|
{"tryOut", 1, nifTryOut1}, |
|
|
|
{"tryOut", 2, nifTryOut2}, |
|
|
|
{"tryOuts", 2, nifTryOuts2}, |
|
|
|
{"tryOuts", 3, nifTryOuts3}, |
|
|
|
{"tryOutByProd", 2, nifTryOutByProd2}, |
|
|
|
{"tryOutsByProd", 3, nifTryOutByProd3}, |
|
|
|
{"size", 1, nifSize1} |
|
|
|
}; |
|
|
|
|
|
|
|
ERL_NIF_INIT(eLfq, nif_funcs, on_nif_load, NULL, on_nif_upgrade, on_nif_unload |
|
|
|
ERL_NIF_INIT(eLfq, nifFuncs, nifLoad, NULL, nifUpgrade, nifUnload |
|
|
|
) |