jmcore: one JACK client per pair
This reduces latency because JACK client is run only once per cycle
This commit is contained in:
parent
da63d80b84
commit
919e01fd8b
281
jmcore.c
281
jmcore.c
|
@ -40,17 +40,14 @@ extern const struct dbus_interface_descriptor g_interface;
|
||||||
static const char * g_dbus_unique_name;
|
static const char * g_dbus_unique_name;
|
||||||
static dbus_object_path g_object;
|
static dbus_object_path g_object;
|
||||||
static bool g_quit;
|
static bool g_quit;
|
||||||
static jack_client_t * g_jack_client;
|
|
||||||
static bool g_jack_client_died;
|
|
||||||
|
|
||||||
pthread_mutex_t g_mutex;
|
struct list_head g_pairs;
|
||||||
struct list_head g_new_pairs;
|
|
||||||
struct list_head g_active_pairs;
|
|
||||||
|
|
||||||
struct port_pair
|
struct port_pair
|
||||||
{
|
{
|
||||||
struct list_head siblings;
|
struct list_head siblings;
|
||||||
bool active;
|
jack_client_t * client;
|
||||||
|
bool dead;
|
||||||
bool midi;
|
bool midi;
|
||||||
jack_port_t * input_port;
|
jack_port_t * input_port;
|
||||||
jack_port_t * output_port;
|
jack_port_t * output_port;
|
||||||
|
@ -58,94 +55,66 @@ struct port_pair
|
||||||
char * output_port_name;
|
char * output_port_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define pair_ptr ((struct port_pair *)arg)
|
||||||
|
|
||||||
void shutdown_callback(void * arg)
|
void shutdown_callback(void * arg)
|
||||||
{
|
{
|
||||||
g_jack_client_died = true;
|
pair_ptr->dead = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int process_callback(jack_nframes_t nframes, void * arg)
|
int process_callback(jack_nframes_t nframes, void * arg)
|
||||||
{
|
{
|
||||||
struct list_head * node_ptr;
|
|
||||||
struct list_head * temp_node_ptr;
|
|
||||||
struct port_pair * pair_ptr;
|
|
||||||
bool locked;
|
|
||||||
void * input;
|
void * input;
|
||||||
void * output;
|
void * output;
|
||||||
jack_midi_event_t midi_event;
|
jack_midi_event_t midi_event;
|
||||||
jack_nframes_t midi_event_index;
|
jack_nframes_t midi_event_index;
|
||||||
|
|
||||||
locked = pthread_mutex_trylock(&g_mutex) == 0;
|
input = jack_port_get_buffer(pair_ptr->input_port, nframes);
|
||||||
|
output = jack_port_get_buffer(pair_ptr->output_port, nframes);
|
||||||
|
|
||||||
if (locked)
|
if (!pair_ptr->midi)
|
||||||
{
|
{
|
||||||
while (!list_empty(&g_new_pairs))
|
memcpy(output, input, nframes * sizeof(jack_default_audio_sample_t));
|
||||||
{
|
|
||||||
node_ptr = g_new_pairs.next;
|
|
||||||
list_del(node_ptr);
|
|
||||||
list_add_tail(node_ptr, &g_active_pairs);
|
|
||||||
pair_ptr = list_entry(node_ptr, struct port_pair, siblings);
|
|
||||||
pair_ptr->active = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
list_for_each_safe(node_ptr, temp_node_ptr, &g_active_pairs)
|
|
||||||
{
|
{
|
||||||
pair_ptr = list_entry(node_ptr, struct port_pair, siblings);
|
jack_midi_clear_buffer(output);
|
||||||
if (locked && !pair_ptr->active)
|
midi_event_index = 0;
|
||||||
|
while (jack_midi_event_get(&midi_event, input, midi_event_index) == 0)
|
||||||
{
|
{
|
||||||
list_del_init(node_ptr);
|
jack_midi_event_write(output, midi_event.time, midi_event.buffer, midi_event.size);
|
||||||
continue;
|
midi_event_index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
input = jack_port_get_buffer(pair_ptr->input_port, nframes);
|
|
||||||
output = jack_port_get_buffer(pair_ptr->output_port, nframes);
|
|
||||||
|
|
||||||
if (!pair_ptr->midi)
|
|
||||||
{
|
|
||||||
memcpy(output, input, nframes * sizeof(jack_default_audio_sample_t));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
jack_midi_clear_buffer(output);
|
|
||||||
midi_event_index = 0;
|
|
||||||
while (jack_midi_event_get(&midi_event, input, midi_event_index) == 0)
|
|
||||||
{
|
|
||||||
jack_midi_event_write(output, midi_event.time, midi_event.buffer, midi_event.size);
|
|
||||||
midi_event_index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locked)
|
|
||||||
{
|
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void free_pair(struct port_pair * pair_ptr)
|
#undef pair_ptr
|
||||||
{
|
|
||||||
if (g_jack_client != NULL)
|
|
||||||
{
|
|
||||||
jack_port_unregister(g_jack_client, pair_ptr->input_port);
|
|
||||||
jack_port_unregister(g_jack_client, pair_ptr->output_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
static void destroy_pair(struct port_pair * pair_ptr)
|
||||||
|
{
|
||||||
|
list_del(&pair_ptr->siblings);
|
||||||
|
jack_client_close(pair_ptr->client);
|
||||||
free(pair_ptr->input_port_name);
|
free(pair_ptr->input_port_name);
|
||||||
free(pair_ptr->output_port_name);
|
free(pair_ptr->output_port_name);
|
||||||
free(pair_ptr);
|
free(pair_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void eat_pair_list(struct list_head * list)
|
static void bury_zombie_pairs(void)
|
||||||
{
|
{
|
||||||
struct list_head * node_ptr;
|
struct list_head * node_ptr;
|
||||||
|
struct list_head * temp_node_ptr;
|
||||||
|
struct port_pair * pair_ptr;
|
||||||
|
|
||||||
while (!list_empty(list))
|
list_for_each_safe(node_ptr, temp_node_ptr, &g_pairs)
|
||||||
{
|
{
|
||||||
node_ptr = list->next;
|
pair_ptr = list_entry(node_ptr, struct port_pair, siblings);
|
||||||
list_del(node_ptr);
|
if (pair_ptr->dead)
|
||||||
free_pair(list_entry(node_ptr, struct port_pair, siblings));
|
{
|
||||||
|
log_info("Bury zombie '%s':'%s'", pair_ptr->input_port_name, pair_ptr->output_port_name);
|
||||||
|
destroy_pair(pair_ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,16 +212,7 @@ int main(int argc, char ** argv)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
INIT_LIST_HEAD(&g_new_pairs);
|
INIT_LIST_HEAD(&g_pairs);
|
||||||
INIT_LIST_HEAD(&g_active_pairs);
|
|
||||||
|
|
||||||
ret = pthread_mutex_init(&g_mutex, NULL);
|
|
||||||
if (ret != 0)
|
|
||||||
{
|
|
||||||
log_error("Mutex initialization failed with %d.", ret);
|
|
||||||
ret = 1;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
install_term_signal_handler(SIGTERM, false);
|
install_term_signal_handler(SIGTERM, false);
|
||||||
install_term_signal_handler(SIGINT, true);
|
install_term_signal_handler(SIGINT, true);
|
||||||
|
@ -260,74 +220,36 @@ int main(int argc, char ** argv)
|
||||||
if (!connect_dbus())
|
if (!connect_dbus())
|
||||||
{
|
{
|
||||||
log_error("Failed to connect to D-Bus");
|
log_error("Failed to connect to D-Bus");
|
||||||
ret = 1;
|
return 1;
|
||||||
goto destroy_mutex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!g_quit)
|
while (!g_quit)
|
||||||
{
|
{
|
||||||
dbus_connection_read_write_dispatch(g_dbus_connection, 50);
|
dbus_connection_read_write_dispatch(g_dbus_connection, 50);
|
||||||
|
bury_zombie_pairs();
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
||||||
eat_pair_list(&g_new_pairs);
|
while (!list_empty(&g_pairs))
|
||||||
eat_pair_list(&g_active_pairs);
|
|
||||||
|
|
||||||
if (g_jack_client != NULL)
|
|
||||||
{
|
{
|
||||||
jack_deactivate(g_jack_client);
|
destroy_pair(list_entry(g_pairs.next, struct port_pair, siblings));
|
||||||
jack_client_close(g_jack_client);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect_dbus();
|
disconnect_dbus();
|
||||||
destroy_mutex:
|
return 0;
|
||||||
pthread_mutex_destroy(&g_mutex);
|
|
||||||
exit:
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void jmcore_connect_to_jack(struct dbus_method_call * call_ptr)
|
/***************************************************************************/
|
||||||
|
/* D-Bus interface implementation */
|
||||||
|
|
||||||
|
static void jmcore_get_pid(struct dbus_method_call * call_ptr)
|
||||||
{
|
{
|
||||||
int ret;
|
dbus_int64_t pid;
|
||||||
const char * jack_client_name;
|
|
||||||
|
|
||||||
if (g_jack_client != NULL)
|
pid = getpid();
|
||||||
{
|
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server is already connected");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_jack_client = jack_client_open("jmcore", JackNoStartServer, NULL);
|
method_return_new_single(call_ptr, DBUS_TYPE_INT64, &pid);
|
||||||
if (g_jack_client == NULL)
|
|
||||||
{
|
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Cannot connect to JACK server");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = jack_set_process_callback(g_jack_client, process_callback, NULL);
|
|
||||||
if (ret != 0)
|
|
||||||
{
|
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK process callback setup failed");
|
|
||||||
jack_client_close(g_jack_client);
|
|
||||||
g_jack_client = NULL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
jack_on_shutdown(g_jack_client, shutdown_callback, NULL);
|
|
||||||
|
|
||||||
ret = jack_activate(g_jack_client);
|
|
||||||
if (ret != 0)
|
|
||||||
{
|
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK client activation failed");
|
|
||||||
jack_client_close(g_jack_client);
|
|
||||||
g_jack_client = NULL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
jack_client_name = jack_get_client_name(g_jack_client);
|
|
||||||
|
|
||||||
method_return_new_single(call_ptr, DBUS_TYPE_STRING, &jack_client_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void jmcore_create(struct dbus_method_call * call_ptr)
|
static void jmcore_create(struct dbus_method_call * call_ptr)
|
||||||
|
@ -336,12 +258,7 @@ static void jmcore_create(struct dbus_method_call * call_ptr)
|
||||||
const char * input;
|
const char * input;
|
||||||
const char * output;
|
const char * output;
|
||||||
struct port_pair * pair_ptr;
|
struct port_pair * pair_ptr;
|
||||||
|
int ret;
|
||||||
if (g_jack_client == NULL || g_jack_client_died)
|
|
||||||
{
|
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server is not connected");
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
dbus_error_init(&g_dbus_error);
|
dbus_error_init(&g_dbus_error);
|
||||||
if (!dbus_message_get_args(
|
if (!dbus_message_get_args(
|
||||||
|
@ -378,52 +295,59 @@ static void jmcore_create(struct dbus_method_call * call_ptr)
|
||||||
goto free_input_name;
|
goto free_input_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair_ptr->input_port = jack_port_register(g_jack_client, input, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
|
pair_ptr->client = jack_client_open("jmcore", JackNoStartServer, NULL);
|
||||||
|
if (pair_ptr->client == NULL)
|
||||||
|
{
|
||||||
|
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Cannot connect to JACK server");
|
||||||
|
goto free_output_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pair_ptr->midi = midi;
|
||||||
|
pair_ptr->dead = false;
|
||||||
|
|
||||||
|
ret = jack_set_process_callback(pair_ptr->client, process_callback, pair_ptr);
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK process callback setup failed");
|
||||||
|
goto close_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_on_shutdown(pair_ptr->client, shutdown_callback, pair_ptr);
|
||||||
|
|
||||||
|
pair_ptr->input_port = jack_port_register(pair_ptr->client, input, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
|
||||||
if (pair_ptr->input_port == NULL)
|
if (pair_ptr->input_port == NULL)
|
||||||
{
|
{
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Port '%s' registration failed.", input);
|
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Port '%s' registration failed.", input);
|
||||||
goto free_output_name;
|
goto free_output_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair_ptr->output_port = jack_port_register(g_jack_client, output, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
pair_ptr->output_port = jack_port_register(pair_ptr->client, output, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
||||||
if (pair_ptr->input_port == NULL)
|
if (pair_ptr->input_port == NULL)
|
||||||
{
|
{
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Port '%s' registration failed.", output);
|
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Port '%s' registration failed.", output);
|
||||||
goto unregister_input_port;
|
goto unregister_input_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair_ptr->midi = midi;
|
list_add_tail(&pair_ptr->siblings, &g_pairs);
|
||||||
|
|
||||||
pair_ptr->active = false;
|
ret = jack_activate(pair_ptr->client);
|
||||||
pthread_mutex_lock(&g_mutex);
|
if (ret != 0)
|
||||||
list_add_tail(&pair_ptr->siblings, &g_new_pairs);
|
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
|
|
||||||
/* wait pair to become active */
|
|
||||||
pthread_mutex_lock(&g_mutex);
|
|
||||||
while (!pair_ptr->active)
|
|
||||||
{
|
{
|
||||||
if (g_jack_client_died)
|
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK client activation failed");
|
||||||
{
|
goto remove_from_list;
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server died.");
|
|
||||||
list_del(&pair_ptr->siblings);
|
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
goto unregister_output_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
usleep(50);
|
|
||||||
pthread_mutex_lock(&g_mutex);
|
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
|
|
||||||
method_return_new_void(call_ptr);
|
method_return_new_void(call_ptr);
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
unregister_output_port:
|
remove_from_list:
|
||||||
jack_port_unregister(g_jack_client, pair_ptr->output_port);
|
list_del(&pair_ptr->siblings);
|
||||||
|
//unregister_output_port:
|
||||||
|
jack_port_unregister(pair_ptr->client, pair_ptr->output_port);
|
||||||
unregister_input_port:
|
unregister_input_port:
|
||||||
jack_port_unregister(g_jack_client, pair_ptr->input_port);
|
jack_port_unregister(pair_ptr->client, pair_ptr->input_port);
|
||||||
|
close_client:
|
||||||
|
jack_client_close(pair_ptr->client);
|
||||||
free_output_name:
|
free_output_name:
|
||||||
free(pair_ptr->output_port_name);
|
free(pair_ptr->output_port_name);
|
||||||
free_input_name:
|
free_input_name:
|
||||||
|
@ -440,12 +364,6 @@ static void jmcore_destroy(struct dbus_method_call * call_ptr)
|
||||||
struct list_head * node_ptr;
|
struct list_head * node_ptr;
|
||||||
struct port_pair * pair_ptr;
|
struct port_pair * pair_ptr;
|
||||||
|
|
||||||
if (g_jack_client == NULL || g_jack_client_died)
|
|
||||||
{
|
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server is not connected");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dbus_error_init(&g_dbus_error);
|
dbus_error_init(&g_dbus_error);
|
||||||
if (!dbus_message_get_args(call_ptr->message, &g_dbus_error, DBUS_TYPE_STRING, &port, DBUS_TYPE_INVALID))
|
if (!dbus_message_get_args(call_ptr->message, &g_dbus_error, DBUS_TYPE_STRING, &port, DBUS_TYPE_INVALID))
|
||||||
{
|
{
|
||||||
|
@ -454,49 +372,20 @@ static void jmcore_destroy(struct dbus_method_call * call_ptr)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_lock(&g_mutex);
|
list_for_each(node_ptr, &g_pairs)
|
||||||
list_for_each(node_ptr, &g_active_pairs)
|
|
||||||
{
|
{
|
||||||
pair_ptr = list_entry(node_ptr, struct port_pair, siblings);
|
pair_ptr = list_entry(node_ptr, struct port_pair, siblings);
|
||||||
if (strcmp(pair_ptr->input_port_name, port) == 0 ||
|
if (strcmp(pair_ptr->input_port_name, port) == 0 ||
|
||||||
strcmp(pair_ptr->output_port_name, port) == 0)
|
strcmp(pair_ptr->output_port_name, port) == 0)
|
||||||
{
|
{
|
||||||
goto found;
|
destroy_pair(pair_ptr);
|
||||||
|
method_return_new_void(call_ptr);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
|
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_INVALID_ARGS, "port '%s' not found.", port);
|
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_INVALID_ARGS, "port '%s' not found.", port);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
found:
|
|
||||||
pair_ptr->active = false;
|
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
|
|
||||||
/* wait pair to become non-active */
|
|
||||||
pthread_mutex_lock(&g_mutex);
|
|
||||||
while (!list_empty(&pair_ptr->siblings))
|
|
||||||
{
|
|
||||||
if (g_jack_client_died)
|
|
||||||
{
|
|
||||||
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server died.");
|
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
if (list_empty(&pair_ptr->siblings))
|
|
||||||
{
|
|
||||||
free_pair(pair_ptr);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
usleep(50);
|
|
||||||
pthread_mutex_lock(&g_mutex);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_mutex);
|
|
||||||
|
|
||||||
free_pair(pair_ptr);
|
|
||||||
|
|
||||||
method_return_new_void(call_ptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void jmcore_exit(struct dbus_method_call * call_ptr)
|
static void jmcore_exit(struct dbus_method_call * call_ptr)
|
||||||
|
@ -506,8 +395,8 @@ static void jmcore_exit(struct dbus_method_call * call_ptr)
|
||||||
method_return_new_void(call_ptr);
|
method_return_new_void(call_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
METHOD_ARGS_BEGIN(connect_to_jack, "Connect to JACK server")
|
METHOD_ARGS_BEGIN(get_pid, "Get process ID")
|
||||||
METHOD_ARG_DESCRIBE_OUT("jack_client_name", "s", "JACK client name")
|
METHOD_ARG_DESCRIBE_OUT("process_id", DBUS_TYPE_INT64_AS_STRING, "Process ID")
|
||||||
METHOD_ARGS_END
|
METHOD_ARGS_END
|
||||||
|
|
||||||
METHOD_ARGS_BEGIN(create, "Create port pair")
|
METHOD_ARGS_BEGIN(create, "Create port pair")
|
||||||
|
@ -524,7 +413,7 @@ METHOD_ARGS_BEGIN(exit, "Tell jmcore D-Bus service to exit")
|
||||||
METHOD_ARGS_END
|
METHOD_ARGS_END
|
||||||
|
|
||||||
METHODS_BEGIN
|
METHODS_BEGIN
|
||||||
METHOD_DESCRIBE(connect_to_jack, jmcore_connect_to_jack)
|
METHOD_DESCRIBE(get_pid, jmcore_get_pid)
|
||||||
METHOD_DESCRIBE(create, jmcore_create)
|
METHOD_DESCRIBE(create, jmcore_create)
|
||||||
METHOD_DESCRIBE(destroy, jmcore_destroy)
|
METHOD_DESCRIBE(destroy, jmcore_destroy)
|
||||||
METHOD_DESCRIBE(exit, jmcore_exit)
|
METHOD_DESCRIBE(exit, jmcore_exit)
|
||||||
|
|
Loading…
Reference in New Issue